mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	fix(trace): allow typing in selector w/ frames (#26919)
This commit is contained in:
		
							parent
							
								
									ea4974ce36
								
							
						
					
					
						commit
						d65da74b8f
					
				@ -53,7 +53,7 @@ export class Recorder {
 | 
			
		||||
      console.error('Recorder script ready for test'); // eslint-disable-line no-console
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  refreshListenersIfNeeded() {
 | 
			
		||||
  installListeners() {
 | 
			
		||||
    // Ensure we are attached to the current document, and we are on top (last element);
 | 
			
		||||
    if (this._highlight.isInstalled())
 | 
			
		||||
      return;
 | 
			
		||||
@ -74,19 +74,24 @@ export class Recorder {
 | 
			
		||||
          return;
 | 
			
		||||
        this._hoveredModel = null;
 | 
			
		||||
        this._highlight.hideActionPoint();
 | 
			
		||||
        this._updateHighlight();
 | 
			
		||||
        this._updateHighlight(false);
 | 
			
		||||
      }, true),
 | 
			
		||||
    ];
 | 
			
		||||
    this._highlight.install();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uninstallListeners() {
 | 
			
		||||
    removeEventListeners(this._listeners);
 | 
			
		||||
    this._highlight.uninstall();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setUIState(state: UIState, delegate: RecorderDelegate) {
 | 
			
		||||
    this._delegate = delegate;
 | 
			
		||||
 | 
			
		||||
    if (state.mode !== 'none' || state.actionSelector)
 | 
			
		||||
      this.refreshListenersIfNeeded();
 | 
			
		||||
      this.installListeners();
 | 
			
		||||
    else
 | 
			
		||||
      removeEventListeners(this._listeners);
 | 
			
		||||
      this.uninstallListeners();
 | 
			
		||||
 | 
			
		||||
    const { mode, actionPoint, actionSelector, language, testIdAttributeName } = state;
 | 
			
		||||
    this._testIdAttributeName = testIdAttributeName;
 | 
			
		||||
@ -113,7 +118,7 @@ export class Recorder {
 | 
			
		||||
 | 
			
		||||
    if (actionSelector !== this._actionSelector) {
 | 
			
		||||
      this._hoveredModel = actionSelector ? querySelector(this._injectedScript, actionSelector, this.document) : null;
 | 
			
		||||
      this._updateHighlight();
 | 
			
		||||
      this._updateHighlight(false);
 | 
			
		||||
      this._actionSelector = actionSelector;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -121,7 +126,7 @@ export class Recorder {
 | 
			
		||||
  clearHighlight() {
 | 
			
		||||
    this._hoveredModel = null;
 | 
			
		||||
    this._activeModel = null;
 | 
			
		||||
    this._updateHighlight();
 | 
			
		||||
    this._updateHighlight(false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _actionInProgress(event: Event): boolean {
 | 
			
		||||
@ -257,7 +262,7 @@ export class Recorder {
 | 
			
		||||
    if (!this._hoveredElement || !this._hoveredElement.isConnected) {
 | 
			
		||||
      this._hoveredModel = null;
 | 
			
		||||
      this._hoveredElement = null;
 | 
			
		||||
      this._updateHighlight();
 | 
			
		||||
      this._updateHighlight(true);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const hoveredElement = this._hoveredElement;
 | 
			
		||||
@ -265,14 +270,14 @@ export class Recorder {
 | 
			
		||||
    if ((this._hoveredModel && this._hoveredModel.selector === selector))
 | 
			
		||||
      return;
 | 
			
		||||
    this._hoveredModel = selector ? { selector, elements } : null;
 | 
			
		||||
    this._updateHighlight();
 | 
			
		||||
    this._updateHighlight(true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _updateHighlight() {
 | 
			
		||||
  private _updateHighlight(userGesture: boolean) {
 | 
			
		||||
    const elements = this._hoveredModel ? this._hoveredModel.elements : [];
 | 
			
		||||
    const selector = this._hoveredModel ? this._hoveredModel.selector : '';
 | 
			
		||||
    this._highlight.updateHighlight(elements, selector, this._mode === 'recording');
 | 
			
		||||
    if (this._hoveredModel)
 | 
			
		||||
    if (userGesture)
 | 
			
		||||
      this._delegate.highlightUpdated?.();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -522,7 +527,7 @@ export class PollingRecorder implements RecorderDelegate {
 | 
			
		||||
    this._recorder = new Recorder(injectedScript);
 | 
			
		||||
    this._embedder = injectedScript.window as any;
 | 
			
		||||
 | 
			
		||||
    injectedScript.onGlobalListenersRemoved.add(() => this._recorder.refreshListenersIfNeeded());
 | 
			
		||||
    injectedScript.onGlobalListenersRemoved.add(() => this._recorder.installListeners());
 | 
			
		||||
 | 
			
		||||
    const refreshOverlay = () => {
 | 
			
		||||
      this._pollRecorderMode().catch(e => console.log(e)); // eslint-disable-line no-console
 | 
			
		||||
 | 
			
		||||
@ -228,8 +228,9 @@ export const InspectModeController: React.FunctionComponent<{
 | 
			
		||||
}> = ({ iframe, isInspecting, sdkLanguage, testIdAttributeName, highlightedLocator, setHighlightedLocator, iteration }) => {
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    const recorders: { recorder: Recorder, frameSelector: string }[] = [];
 | 
			
		||||
    const isUnderTest = new URLSearchParams(window.location.search).get('isUnderTest') === 'true';
 | 
			
		||||
    try {
 | 
			
		||||
      createRecorders(recorders, sdkLanguage, testIdAttributeName, '', iframe?.contentWindow);
 | 
			
		||||
      createRecorders(recorders, sdkLanguage, testIdAttributeName, isUnderTest, '', iframe?.contentWindow);
 | 
			
		||||
    } catch {
 | 
			
		||||
      // Potential cross-origin exceptions.
 | 
			
		||||
    }
 | 
			
		||||
@ -238,7 +239,7 @@ export const InspectModeController: React.FunctionComponent<{
 | 
			
		||||
      const actionSelector = locatorOrSelectorAsSelector(sdkLanguage, highlightedLocator, testIdAttributeName);
 | 
			
		||||
      recorder.setUIState({
 | 
			
		||||
        mode: isInspecting ? 'inspecting' : 'none',
 | 
			
		||||
        actionSelector,
 | 
			
		||||
        actionSelector: actionSelector.startsWith(frameSelector) ? actionSelector.substring(frameSelector.length).trim() : undefined,
 | 
			
		||||
        language: sdkLanguage,
 | 
			
		||||
        testIdAttributeName,
 | 
			
		||||
      }, {
 | 
			
		||||
@ -257,12 +258,12 @@ export const InspectModeController: React.FunctionComponent<{
 | 
			
		||||
  return <></>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function createRecorders(recorders: { recorder: Recorder, frameSelector: string }[], sdkLanguage: Language, testIdAttributeName: string, parentFrameSelector: string, frameWindow: Window | null | undefined) {
 | 
			
		||||
function createRecorders(recorders: { recorder: Recorder, frameSelector: string }[], sdkLanguage: Language, testIdAttributeName: string, isUnderTest: boolean, parentFrameSelector: string, frameWindow: Window | null | undefined) {
 | 
			
		||||
  if (!frameWindow)
 | 
			
		||||
    return;
 | 
			
		||||
  const win = frameWindow as any;
 | 
			
		||||
  if (!win._recorder) {
 | 
			
		||||
    const injectedScript = new InjectedScript(frameWindow as any, false, sdkLanguage, testIdAttributeName, 1, 'chromium', []);
 | 
			
		||||
    const injectedScript = new InjectedScript(frameWindow as any, isUnderTest, sdkLanguage, testIdAttributeName, 1, 'chromium', []);
 | 
			
		||||
    const recorder = new Recorder(injectedScript);
 | 
			
		||||
    win._injectedScript = injectedScript;
 | 
			
		||||
    win._recorder = { recorder, frameSelector: parentFrameSelector };
 | 
			
		||||
@ -272,7 +273,7 @@ function createRecorders(recorders: { recorder: Recorder, frameSelector: string
 | 
			
		||||
  for (let i = 0; i < frameWindow.frames.length; ++i) {
 | 
			
		||||
    const childFrame = frameWindow.frames[i];
 | 
			
		||||
    const frameSelector = childFrame.frameElement ? win._injectedScript.generateSelector(childFrame.frameElement, { omitInternalEngines: true, testIdAttributeName }) + ' >> internal:control=enter-frame >> ' : '';
 | 
			
		||||
    createRecorders(recorders, sdkLanguage, testIdAttributeName, parentFrameSelector + frameSelector, childFrame);
 | 
			
		||||
    createRecorders(recorders, sdkLanguage, testIdAttributeName, isUnderTest, parentFrameSelector + frameSelector, childFrame);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1034,3 +1034,58 @@ test('should pick locator in iframe', async ({ page, runAndTrace, server }) => {
 | 
			
		||||
  await snapshot.frameLocator('#frame1').frameLocator('iframe').frameLocator('[name=two]').getByText('HelloNameTwo').click();
 | 
			
		||||
  await expect.soft(cmWrapper).toContainText(`frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="two"]').getByText('HelloNameTwo')`, { timeout: 0 });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should highlight locator in iframe while typing', async ({ page, runAndTrace, server, platform }) => {
 | 
			
		||||
  /*
 | 
			
		||||
    iframe[id=frame1]
 | 
			
		||||
      div Hello1
 | 
			
		||||
      iframe
 | 
			
		||||
        div Hello2
 | 
			
		||||
        iframe[name=one]
 | 
			
		||||
          div HelloNameOne
 | 
			
		||||
        iframe[name=two]
 | 
			
		||||
          dev HelloNameTwo
 | 
			
		||||
  */
 | 
			
		||||
  const traceViewer = await runAndTrace(async () => {
 | 
			
		||||
    await page.goto(server.EMPTY_PAGE);
 | 
			
		||||
    await page.setContent(`<iframe id=frame1 srcdoc="<div>Hello1</div><iframe srcdoc='<div>Hello2</div><iframe name=one></iframe><iframe name=two></iframe><iframe></iframe>'>">`);
 | 
			
		||||
    const frameOne = page.frame({ name: 'one' });
 | 
			
		||||
    await frameOne.setContent(`<div>HelloNameOne</div>`);
 | 
			
		||||
    const frameTwo = page.frame({ name: 'two' });
 | 
			
		||||
    await frameTwo.setContent(`<div>HelloNameTwo</div>`);
 | 
			
		||||
    await page.evaluate('2+2');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const snapshot = await traceViewer.snapshotFrame('page.evaluate');
 | 
			
		||||
  await traceViewer.page.getByText('Locator').click();
 | 
			
		||||
  await traceViewer.page.locator('.CodeMirror').click();
 | 
			
		||||
 | 
			
		||||
  const locators = [{
 | 
			
		||||
    text: `frameLocator('#frame1').getByText('Hello1')`,
 | 
			
		||||
    element: snapshot.frameLocator('#frame1').locator('div', { hasText: 'Hello1' }),
 | 
			
		||||
    highlight: snapshot.frameLocator('#frame1').locator('x-pw-highlight'),
 | 
			
		||||
  }, {
 | 
			
		||||
    text: `frameLocator('#frame1').frameLocator('iframe').getByText('Hello2')`,
 | 
			
		||||
    element: snapshot.frameLocator('#frame1').frameLocator('iframe').locator('div', { hasText: 'Hello2' }),
 | 
			
		||||
    highlight: snapshot.frameLocator('#frame1').frameLocator('iframe').locator('x-pw-highlight'),
 | 
			
		||||
  }, {
 | 
			
		||||
    text: `frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="one"]').getByText('HelloNameOne')`,
 | 
			
		||||
    element: snapshot.frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="one"]').locator('div', { hasText: 'HelloNameOne' }),
 | 
			
		||||
    highlight: snapshot.frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="one"]').locator('x-pw-highlight'),
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  for (const locator of locators) {
 | 
			
		||||
    if (platform === 'darwin')
 | 
			
		||||
      await traceViewer.page.keyboard.press('Meta+a');
 | 
			
		||||
    else
 | 
			
		||||
      await traceViewer.page.keyboard.press('Control+a');
 | 
			
		||||
    await traceViewer.page.keyboard.press('Backspace');
 | 
			
		||||
    await traceViewer.page.keyboard.type(locator.text);
 | 
			
		||||
    const elementBox = await locator.element.boundingBox();
 | 
			
		||||
    const highlightBox = await locator.highlight.boundingBox();
 | 
			
		||||
    expect(Math.abs(elementBox.width - highlightBox.width)).toBeLessThan(5);
 | 
			
		||||
    expect(Math.abs(elementBox.height - highlightBox.height)).toBeLessThan(5);
 | 
			
		||||
    expect(Math.abs(elementBox.x - highlightBox.x)).toBeLessThan(5);
 | 
			
		||||
    expect(Math.abs(elementBox.y - highlightBox.y)).toBeLessThan(5);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user