[go: nahoru, domu]

blob: d545cc3c3ecc92b0e448f7c31dcbba0b0fe39355 [file] [log] [blame]
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/es_modules_import */
import {type SelectorType} from '../../../third_party/puppeteer-replay/puppeteer-replay.js';
import {type Logger} from './Logger.js';
import {MonotonicArray} from './MonotonicArray.js';
import {
computeARIASelector,
type AccessibilityBindings,
} from './selectors/ARIASelector.js';
import {computeCSSSelector} from './selectors/CSSSelector.js';
import {computePierceSelector} from './selectors/PierceSelector.js';
import {type Selector} from './selectors/Selector.js';
import {computeTextSelector} from './selectors/TextSelector.js';
import {computeXPath} from './selectors/XPath.js';
const prefixSelector = (
selector: Selector|undefined,
prefix: string,
): Selector|undefined => {
if (selector === undefined) {
return;
}
if (typeof selector === 'string') {
return `${prefix}/${selector}`;
}
return selector.map(selector => `${prefix}/${selector}`);
};
export class SelectorComputer {
#customAttributes = [
// Most common attributes first.
'data-testid',
'data-test',
'data-qa',
'data-cy',
'data-test-id',
'data-qa-id',
'data-testing',
];
#bindings: AccessibilityBindings;
#logger: Logger;
#nodes = new MonotonicArray<Node>();
#selectorFunctionsInOrder: Array<(node: Node) => Selector | undefined>;
constructor(
bindings: AccessibilityBindings,
logger: Logger,
customAttribute = '',
selectorTypesToRecord?: SelectorType[],
) {
this.#bindings = bindings;
this.#logger = logger;
let selectorOrder = [
'aria',
'css',
'xpath',
'pierce',
'text',
] as SelectorType[];
if (customAttribute) {
// Custom DOM attributes indicate a preference for CSS/XPath selectors.
this.#customAttributes.unshift(customAttribute);
selectorOrder = [
'css',
'xpath',
'pierce',
'aria',
'text',
] as SelectorType[];
}
this.#selectorFunctionsInOrder = selectorOrder
.filter(type => {
if (selectorTypesToRecord) {
return selectorTypesToRecord.includes(type);
}
return true;
})
.map(selectorType => {
switch (selectorType) {
case 'css':
return this.getCSSSelector.bind(this);
case 'xpath':
return this.getXPathSelector.bind(this);
case 'pierce':
return this.getPierceSelector.bind(this);
case 'aria':
return this.getARIASelector.bind(this);
case 'text':
return this.getTextSelector.bind(this);
default:
throw new Error('Unknown selector type: ' + selectorType);
}
});
}
getSelectors(node: Node): Selector[] {
const selectors: Selector[] = [];
for (const getSelector of this.#selectorFunctionsInOrder) {
const selector = getSelector(node);
if (selector) {
selectors.push(selector);
}
}
return selectors;
}
getCSSSelector(node: Node): Selector|undefined {
return this.#logger.timed(`getCSSSelector: ${this.#nodes.getOrInsert(node)} ${node.nodeName}`, () => {
return computeCSSSelector(node, this.#customAttributes);
});
}
getTextSelector(node: Node): Selector|undefined {
return this.#logger.timed(`getTextSelector: ${this.#nodes.getOrInsert(node)} ${node.nodeName}`, () => {
return prefixSelector(computeTextSelector(node), 'text');
});
}
getXPathSelector(node: Node): Selector|undefined {
return this.#logger.timed(`getXPathSelector: ${this.#nodes.getOrInsert(node)} ${node.nodeName}`, () => {
return prefixSelector(
computeXPath(node, true, this.#customAttributes),
'xpath',
);
});
}
getPierceSelector(node: Node): Selector|undefined {
return this.#logger.timed(`getPierceSelector: ${this.#nodes.getOrInsert(node)} ${node.nodeName}`, () => {
return prefixSelector(
computePierceSelector(node, this.#customAttributes),
'pierce',
);
});
}
getARIASelector(node: Node): Selector|undefined {
return this.#logger.timed(`getARIASelector: ${this.#nodes.getOrInsert(node)} ${node.nodeName}`, () => {
return prefixSelector(computeARIASelector(node, this.#bindings), 'aria');
});
}
}