fix(debug controller): highlight selectors in iframe
This commit is contained in:
parent
67471cb3c5
commit
95a7a757f0
|
|
@ -19,7 +19,7 @@ import { XPathEngine } from './xpathSelectorEngine';
|
|||
import { ReactEngine } from './reactSelectorEngine';
|
||||
import { VueEngine } from './vueSelectorEngine';
|
||||
import { createRoleEngine } from './roleSelectorEngine';
|
||||
import { parseAttributeSelector } from '../../utils/isomorphic/selectorParser';
|
||||
import { parseAttributeSelector, splitSelectorByFrame } from '../../utils/isomorphic/selectorParser';
|
||||
import type { NestedSelectorBody, ParsedSelector, ParsedSelectorPart } from '../../utils/isomorphic/selectorParser';
|
||||
import { visitAllSelectorParts, parseSelector, stringifySelector } from '../../utils/isomorphic/selectorParser';
|
||||
import { type TextMatcher, elementMatchesText, elementText, type ElementText, getElementLabels } from './selectorUtils';
|
||||
|
|
@ -166,12 +166,23 @@ export class InjectedScript {
|
|||
return this._testIdAttributeNameForStrictErrorAndConsoleCodegen;
|
||||
}
|
||||
|
||||
parseSelector(selector: string): ParsedSelector {
|
||||
const result = parseSelector(selector);
|
||||
visitAllSelectorParts(result, part => {
|
||||
private _checkForUnknownEngine(parsed: ParsedSelector, selector: string) {
|
||||
visitAllSelectorParts(parsed, part => {
|
||||
if (!this._engines.has(part.name))
|
||||
throw this.createStacklessError(`Unknown engine "${part.name}" while parsing selector ${selector}`);
|
||||
});
|
||||
}
|
||||
|
||||
parseSelector(selector: string): ParsedSelector {
|
||||
const result = parseSelector(selector);
|
||||
this._checkForUnknownEngine(result, selector);
|
||||
return result;
|
||||
}
|
||||
|
||||
splitSelectorByFrame(selector: string): ParsedSelector[] {
|
||||
const result = splitSelectorByFrame(selector);
|
||||
for (const part of result)
|
||||
this._checkForUnknownEngine(part, selector);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import type { ElementInfo, Mode, OverlayState, UIState } from '@recorder/recorde
|
|||
import type { ElementText } from '../selectorUtils';
|
||||
import type { Highlight, HighlightOptions } from '../highlight';
|
||||
import clipPaths from './clipPaths';
|
||||
import { parseSelector, splitSelectorByFrame } from '@isomorphic/selectorParser';
|
||||
|
||||
export interface RecorderDelegate {
|
||||
performAction?(action: actions.PerformOnRecordAction): Promise<void>;
|
||||
|
|
@ -1471,18 +1472,29 @@ function removeEventListeners(listeners: (() => void)[]) {
|
|||
listeners.splice(0, listeners.length);
|
||||
}
|
||||
|
||||
function frameDepth() {
|
||||
let depth = 0;
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
let w: Window = window;
|
||||
while (w.parent !== w) {
|
||||
w = w.parent;
|
||||
depth++;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
function querySelector(injectedScript: InjectedScript, selector: string, ownerDocument: Document): { selector: string, elements: Element[] } {
|
||||
const empty = { selector, elements: [] };
|
||||
try {
|
||||
const parsedSelector = injectedScript.parseSelector(selector);
|
||||
return {
|
||||
selector,
|
||||
elements: injectedScript.querySelectorAll(parsedSelector, ownerDocument)
|
||||
};
|
||||
// selector starts from the root frame, we need to figure out if this frame is the right one
|
||||
// as a cheap heuristic, we check if it targets the right depth
|
||||
const parts = injectedScript.splitSelectorByFrame(selector);
|
||||
const selectorTargetsFrameDepth = parts.length - 1 === frameDepth();
|
||||
if (selectorTargetsFrameDepth)
|
||||
return { selector, elements: injectedScript.querySelectorAll(parts[parts.length - 1], ownerDocument) };
|
||||
return empty;
|
||||
} catch (e) {
|
||||
return {
|
||||
selector,
|
||||
elements: [],
|
||||
};
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,8 +246,10 @@ export class Recorder implements InstrumentationListener, IRecorder {
|
|||
}
|
||||
|
||||
private _refreshOverlay() {
|
||||
for (const page of this._context.pages())
|
||||
page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()').catch(() => {});
|
||||
for (const page of this._context.pages()) {
|
||||
for (const frame of page.frames())
|
||||
frame.evaluateExpression('window.__pw_refreshOverlay()').catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||
|
|
|
|||
|
|
@ -253,3 +253,22 @@ test('should reset routes before reuse', async ({ server, connectedBrowserFactor
|
|||
await expect(page2).toHaveTitle('console.log test');
|
||||
await browser2.close();
|
||||
});
|
||||
|
||||
test('should highlight inside iframe', async ({ backend, connectedBrowser }, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33146' });
|
||||
|
||||
const context = await connectedBrowser._newContextForReuse();
|
||||
const page = await context.newPage();
|
||||
await backend.navigate({ url: `data:text/html,<iframe srcdoc="<div>bar</div>"/>` });
|
||||
|
||||
|
||||
await page.frameLocator('iframe').getByText('bar').highlight();
|
||||
|
||||
const highlight = page.frameLocator('iframe').locator('x-pw-highlight');
|
||||
await expect(highlight).not.toHaveCount(0);
|
||||
await backend.hideHighlight();
|
||||
await expect(highlight).toHaveCount(0);
|
||||
|
||||
await backend.highlight({ selector: `frameLocator('iframe').getByText('bar')` });
|
||||
await expect(highlight).not.toHaveCount(0);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue