mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore: render typed locators in the recorder (#18162)
This commit is contained in:
		
							parent
							
								
									304a4ee8ec
								
							
						
					
					
						commit
						739b64a09a
					
				@ -637,7 +637,7 @@ This version was also tested against the following stable channels:
 | 
			
		||||
 | 
			
		||||
      Call log:
 | 
			
		||||
        - expect.toBeVisible with timeout 5000ms
 | 
			
		||||
        - waiting for selector "text=Name"
 | 
			
		||||
        - waiting for "getByText('Name')"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        2 |
 | 
			
		||||
 | 
			
		||||
@ -73,7 +73,7 @@ The error would look like this:
 | 
			
		||||
 | 
			
		||||
    Call log:
 | 
			
		||||
      - expect.toBeVisible with timeout 5000ms
 | 
			
		||||
      - waiting for selector "text=Name"
 | 
			
		||||
      - waiting for "getByText('Name')"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      2 |
 | 
			
		||||
 | 
			
		||||
@ -146,7 +146,7 @@ Expected string: "my text"
 | 
			
		||||
Received string: ""
 | 
			
		||||
Call log:
 | 
			
		||||
  - expect.toHaveText with timeout 5000ms
 | 
			
		||||
  - waiting for selector "button"
 | 
			
		||||
  - waiting for "locator('button')"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Set expect timeout in the config
 | 
			
		||||
@ -206,7 +206,7 @@ example.spec.ts:3:1 › basic test ===========================
 | 
			
		||||
 | 
			
		||||
locator.click: Timeout 1000ms exceeded.
 | 
			
		||||
=========================== logs ===========================
 | 
			
		||||
waiting for selector "button"
 | 
			
		||||
waiting for "locator('button')"
 | 
			
		||||
============================================================
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -257,7 +257,7 @@ scheme.LocalUtilsConnectResult = tObject({
 | 
			
		||||
});
 | 
			
		||||
scheme.RootInitializer = tOptional(tObject({}));
 | 
			
		||||
scheme.RootInitializeParams = tObject({
 | 
			
		||||
  sdkLanguage: tString,
 | 
			
		||||
  sdkLanguage: tEnum(['javascript', 'python', 'java', 'csharp']),
 | 
			
		||||
});
 | 
			
		||||
scheme.RootInitializeResult = tObject({
 | 
			
		||||
  playwright: tChannel(['Playwright']),
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ import type { CallMetadata } from './instrumentation';
 | 
			
		||||
import { SdkObject } from './instrumentation';
 | 
			
		||||
import { Artifact } from './artifact';
 | 
			
		||||
import type { Selectors } from './selectors';
 | 
			
		||||
import type { Language } from './isomorphic/locatorGenerators';
 | 
			
		||||
 | 
			
		||||
export interface BrowserProcess {
 | 
			
		||||
  onclose?: ((exitCode: number | null, signal: string | null) => void);
 | 
			
		||||
@ -38,7 +39,7 @@ export type PlaywrightOptions = {
 | 
			
		||||
  rootSdkObject: SdkObject;
 | 
			
		||||
  selectors: Selectors;
 | 
			
		||||
  socksProxyPort?: number;
 | 
			
		||||
  sdkLanguage: string,
 | 
			
		||||
  sdkLanguage: Language,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type BrowserOptions = PlaywrightOptions & {
 | 
			
		||||
 | 
			
		||||
@ -42,6 +42,7 @@ import { isInvalidSelectorError, splitSelectorByFrame, stringifySelector } from
 | 
			
		||||
import type { SelectorInfo } from './selectors';
 | 
			
		||||
import type { ScreenshotOptions } from './screenshotter';
 | 
			
		||||
import type { InputFilesItems } from './dom';
 | 
			
		||||
import { asLocator } from './isomorphic/locatorGenerators';
 | 
			
		||||
 | 
			
		||||
type ContextData = {
 | 
			
		||||
  contextPromise: ManualPromise<dom.FrameExecutionContext | Error>;
 | 
			
		||||
@ -797,7 +798,7 @@ export class Frame extends SdkObject {
 | 
			
		||||
    if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
 | 
			
		||||
      throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
 | 
			
		||||
    return controller.run(async progress => {
 | 
			
		||||
      progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
 | 
			
		||||
      progress.log(`waiting for "${this._asLocator(selector)}"${state === 'attached' ? '' : ' to be ' + state}`);
 | 
			
		||||
      return this.retryWithProgress(progress, selector, options, async (selectorInFrame, continuePolling) => {
 | 
			
		||||
        // Be careful, |this| can be different from |frame|.
 | 
			
		||||
        // We did not pass omitAttached, so it is non-null.
 | 
			
		||||
@ -1107,7 +1108,7 @@ export class Frame extends SdkObject {
 | 
			
		||||
      const { frame, info } = selectorInFrame!;
 | 
			
		||||
      // Be careful, |this| can be different from |frame|.
 | 
			
		||||
      const task = dom.waitForSelectorTask(info, 'attached');
 | 
			
		||||
      progress.log(`waiting for selector "${selector}"`);
 | 
			
		||||
      progress.log(`waiting for "${this._asLocator(selector)}"`);
 | 
			
		||||
      const handle = await frame._scheduleRerunnableHandleTask(progress, info.world, task);
 | 
			
		||||
      const element = handle.asElement() as dom.ElementHandle<Element>;
 | 
			
		||||
      try {
 | 
			
		||||
@ -1500,7 +1501,7 @@ export class Frame extends SdkObject {
 | 
			
		||||
    const callbackText = body.toString();
 | 
			
		||||
    return this.retryWithProgress(progress, selector, options, async selectorInFrame => {
 | 
			
		||||
      // Be careful, |this| can be different from |frame|.
 | 
			
		||||
      progress.log(`waiting for selector "${selector}"`);
 | 
			
		||||
      progress.log(`waiting for "${this._asLocator(selector)}"`);
 | 
			
		||||
      const { frame, info } = selectorInFrame || { frame: this, info: { parsed: { parts: [{ name: 'internal:control', body: 'return-empty', source: 'internal:control=return-empty' }] }, world: 'utility', strict: !!options.strict } };
 | 
			
		||||
      return await frame._scheduleRerunnableTaskInFrame(progress, info, callbackText, taskData, options);
 | 
			
		||||
    });
 | 
			
		||||
@ -1694,7 +1695,7 @@ export class Frame extends SdkObject {
 | 
			
		||||
      progress.cleanupWhenAborted(() => result.dispose());
 | 
			
		||||
      return result;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new Error(`Error: frame navigated while waiting for selector "${selector}"`);
 | 
			
		||||
      throw new Error(`Error: frame navigated while waiting for "${this._asLocator(selector)}"`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1721,6 +1722,10 @@ export class Frame extends SdkObject {
 | 
			
		||||
      }
 | 
			
		||||
    }, { ls: newStorage?.localStorage }).catch(() => {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _asLocator(selector: string) {
 | 
			
		||||
    return asLocator(this._page.context()._browser.options.sdkLanguage, selector);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class RerunnableTask<T> {
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ import { createInstrumentation, SdkObject } from './instrumentation';
 | 
			
		||||
import { debugLogger } from '../common/debugLogger';
 | 
			
		||||
import type { Page } from './page';
 | 
			
		||||
import { DebugController } from './debugController';
 | 
			
		||||
import type { Language } from './isomorphic/locatorGenerators';
 | 
			
		||||
 | 
			
		||||
export class Playwright extends SdkObject {
 | 
			
		||||
  readonly selectors: Selectors;
 | 
			
		||||
@ -40,7 +41,7 @@ export class Playwright extends SdkObject {
 | 
			
		||||
  private _allPages = new Set<Page>();
 | 
			
		||||
  private _allBrowsers = new Set<Browser>();
 | 
			
		||||
 | 
			
		||||
  constructor(sdkLanguage: string, isInternalPlaywright: boolean) {
 | 
			
		||||
  constructor(sdkLanguage: Language, isInternalPlaywright: boolean) {
 | 
			
		||||
    super({ attribution: { isInternalPlaywright }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright');
 | 
			
		||||
    this.instrumentation.addListener({
 | 
			
		||||
      onBrowserOpen: browser => this._allBrowsers.add(browser),
 | 
			
		||||
@ -78,6 +79,6 @@ export class Playwright extends SdkObject {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createPlaywright(sdkLanguage: string, isInternalPlaywright: boolean = false) {
 | 
			
		||||
export function createPlaywright(sdkLanguage: Language, isInternalPlaywright: boolean = false) {
 | 
			
		||||
  return new Playwright(sdkLanguage, isInternalPlaywright);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -472,7 +472,7 @@ export interface RootChannel extends RootEventTarget, Channel {
 | 
			
		||||
  initialize(params: RootInitializeParams, metadata?: Metadata): Promise<RootInitializeResult>;
 | 
			
		||||
}
 | 
			
		||||
export type RootInitializeParams = {
 | 
			
		||||
  sdkLanguage: string,
 | 
			
		||||
  sdkLanguage: 'javascript' | 'python' | 'java' | 'csharp',
 | 
			
		||||
};
 | 
			
		||||
export type RootInitializeOptions = {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -545,7 +545,13 @@ Root:
 | 
			
		||||
 | 
			
		||||
    initialize:
 | 
			
		||||
      parameters:
 | 
			
		||||
        sdkLanguage: string
 | 
			
		||||
        sdkLanguage:
 | 
			
		||||
          type: enum
 | 
			
		||||
          literals:
 | 
			
		||||
          - javascript
 | 
			
		||||
          - python
 | 
			
		||||
          - java
 | 
			
		||||
          - csharp
 | 
			
		||||
      returns:
 | 
			
		||||
        playwright: Playwright
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,2 +1,3 @@
 | 
			
		||||
[*]
 | 
			
		||||
@web/**
 | 
			
		||||
@isomorphic/**
 | 
			
		||||
 | 
			
		||||
@ -18,12 +18,16 @@ import './callLog.css';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import type { CallLog } from './recorderTypes';
 | 
			
		||||
import { msToString } from '@web/uiUtils';
 | 
			
		||||
import { asLocator } from '@isomorphic/locatorGenerators';
 | 
			
		||||
import type { Language } from '@isomorphic/locatorGenerators';
 | 
			
		||||
 | 
			
		||||
export interface CallLogProps {
 | 
			
		||||
  log: CallLog[],
 | 
			
		||||
}
 | 
			
		||||
export type CallLogProps = {
 | 
			
		||||
  language: Language;
 | 
			
		||||
  log: CallLog[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const CallLogView: React.FC<CallLogProps> = ({
 | 
			
		||||
  language,
 | 
			
		||||
  log,
 | 
			
		||||
}) => {
 | 
			
		||||
  const messagesEndRef = React.createRef<HTMLDivElement>();
 | 
			
		||||
@ -36,6 +40,20 @@ export const CallLogView: React.FC<CallLogProps> = ({
 | 
			
		||||
    {log.map(callLog => {
 | 
			
		||||
      const expandOverride = expandOverrides.get(callLog.id);
 | 
			
		||||
      const isExpanded = typeof expandOverride === 'boolean' ? expandOverride : callLog.status !== 'done';
 | 
			
		||||
      const locator = callLog.params.selector ? asLocator(language, callLog.params.selector, false) : null;
 | 
			
		||||
      const locatorCall = `page.${locator}`;
 | 
			
		||||
      let titlePrefix = callLog.title;
 | 
			
		||||
      let titleSuffix = '';
 | 
			
		||||
      if (callLog.title.startsWith('expect.to')) {
 | 
			
		||||
        titlePrefix = 'expect(';
 | 
			
		||||
        titleSuffix = `).${callLog.title.substring('expect.'.length)}()`;
 | 
			
		||||
      } else if (callLog.title.startsWith('locator.')) {
 | 
			
		||||
        titlePrefix = '';
 | 
			
		||||
        titleSuffix = `.${callLog.title.substring('locator.'.length)}()`;
 | 
			
		||||
      } else if (locator || callLog.params.url) {
 | 
			
		||||
        titlePrefix = callLog.title + '(';
 | 
			
		||||
        titleSuffix = ')';
 | 
			
		||||
      }
 | 
			
		||||
      return <div className={`call-log-call ${callLog.status}`} key={callLog.id}>
 | 
			
		||||
        <div className='call-log-call-header'>
 | 
			
		||||
          <span className={`codicon codicon-chevron-${isExpanded ? 'down' : 'right'}`} style={{ cursor: 'pointer' }}onClick={() => {
 | 
			
		||||
@ -43,9 +61,10 @@ export const CallLogView: React.FC<CallLogProps> = ({
 | 
			
		||||
            newOverrides.set(callLog.id, !isExpanded);
 | 
			
		||||
            setExpandOverrides(newOverrides);
 | 
			
		||||
          }}></span>
 | 
			
		||||
          { callLog.title }
 | 
			
		||||
          { callLog.params.url ? <span className='call-log-details'>(<span className='call-log-url' title={callLog.params.url}>{callLog.params.url}</span>)</span> : undefined }
 | 
			
		||||
          { callLog.params.selector ? <span className='call-log-details'>(<span className='call-log-selector' title={callLog.params.selector}>{callLog.params.selector}</span>)</span> : undefined }
 | 
			
		||||
          { titlePrefix }
 | 
			
		||||
          { callLog.params.url ? <span className='call-log-details'><span className='call-log-url' title={callLog.params.url}>{callLog.params.url}</span></span> : undefined }
 | 
			
		||||
          { locator ? <span className='call-log-details'><span className='call-log-selector' title={locatorCall}>{locatorCall}</span></span> : undefined }
 | 
			
		||||
          { titleSuffix }
 | 
			
		||||
          <span className={'codicon ' + iconClass(callLog)}></span>
 | 
			
		||||
          { typeof callLog.duration === 'number' ? <span className='call-log-time'>— {msToString(callLog.duration)}</span> : undefined}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -153,7 +153,7 @@ export const Recorder: React.FC<RecorderProps> = ({
 | 
			
		||||
            copy(selectorInputRef.current?.value || '');
 | 
			
		||||
          }}></ToolbarButton>
 | 
			
		||||
        </Toolbar>
 | 
			
		||||
        <CallLogView log={Array.from(log.values())}/>
 | 
			
		||||
        <CallLogView language={source.language} log={Array.from(log.values())}/>
 | 
			
		||||
      </div>
 | 
			
		||||
    </SplitView>
 | 
			
		||||
  </div>;
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,8 @@
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import type { Language } from '../../playwright-core/src/server/isomorphic/locatorGenerators';
 | 
			
		||||
 | 
			
		||||
export type Point = { x: number, y: number };
 | 
			
		||||
 | 
			
		||||
export type Mode = 'inspecting' | 'recording' | 'none';
 | 
			
		||||
@ -56,7 +58,7 @@ export type Source = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  label: string;
 | 
			
		||||
  text: string;
 | 
			
		||||
  language: string;
 | 
			
		||||
  language: Language;
 | 
			
		||||
  highlight: SourceHighlight[];
 | 
			
		||||
  revealLine?: number;
 | 
			
		||||
  // used to group the language generators
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "useUnknownInCatchVariables": false,
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@isomorphic/*": ["../playwright-core/src/server/isomorphic/*"],
 | 
			
		||||
      "@protocol/*": ["../protocol/src/*"],
 | 
			
		||||
      "@web/*": ["../web/src/*"],
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@ export default defineConfig({
 | 
			
		||||
  ],
 | 
			
		||||
  resolve: {
 | 
			
		||||
    alias: {
 | 
			
		||||
      '@isomorphic': path.resolve(__dirname, '../playwright-core/src/server/isomorphic'),
 | 
			
		||||
      '@protocol': path.resolve(__dirname, '../protocol/src'),
 | 
			
		||||
      '@web': path.resolve(__dirname, '../web/src'),
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -194,7 +194,7 @@ it.describe('pause', () => {
 | 
			
		||||
    await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause();  // 2")');
 | 
			
		||||
    expect(await sanitizeLog(recorderPage)).toEqual([
 | 
			
		||||
      'page.pause- XXms',
 | 
			
		||||
      'page.click(button)- XXms',
 | 
			
		||||
      'page.click(page.locator(\'button\'))- XXms',
 | 
			
		||||
      'page.pause',
 | 
			
		||||
    ]);
 | 
			
		||||
    await recorderPage.click('[title="Resume (F8)"]');
 | 
			
		||||
@ -237,7 +237,7 @@ it.describe('pause', () => {
 | 
			
		||||
    await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause();  // 2")');
 | 
			
		||||
    expect(await sanitizeLog(recorderPage)).toEqual([
 | 
			
		||||
      'page.pause- XXms',
 | 
			
		||||
      'expect.toHaveText(button)- XXms',
 | 
			
		||||
      'expect(page.locator(\'button\')).toHaveText()- XXms',
 | 
			
		||||
      'page.pause',
 | 
			
		||||
    ]);
 | 
			
		||||
    await recorderPage.click('[title="Resume (F8)"]');
 | 
			
		||||
@ -267,7 +267,7 @@ it.describe('pause', () => {
 | 
			
		||||
      await page.pause();
 | 
			
		||||
      await Promise.all([
 | 
			
		||||
        page.waitForEvent('console'),
 | 
			
		||||
        page.click('button'),
 | 
			
		||||
        page.getByRole('button', { name: 'Submit' }).click(),
 | 
			
		||||
      ]);
 | 
			
		||||
      await page.pause();  // 2
 | 
			
		||||
    })();
 | 
			
		||||
@ -277,7 +277,7 @@ it.describe('pause', () => {
 | 
			
		||||
    expect(await sanitizeLog(recorderPage)).toEqual([
 | 
			
		||||
      'page.pause- XXms',
 | 
			
		||||
      'page.waitForEvent(console)',
 | 
			
		||||
      'page.click(button)- XXms',
 | 
			
		||||
      'page.getByRole(\'button\', { name: \'Submit\' }).click()- XXms',
 | 
			
		||||
      'page.pause',
 | 
			
		||||
    ]);
 | 
			
		||||
    await recorderPage.click('[title="Resume (F8)"]');
 | 
			
		||||
@ -288,15 +288,15 @@ it.describe('pause', () => {
 | 
			
		||||
    await page.setContent('<button onclick="console.log(1)">Submit</button>');
 | 
			
		||||
    const scriptPromise = (async () => {
 | 
			
		||||
      await page.pause();
 | 
			
		||||
      await page.isChecked('button');
 | 
			
		||||
      await page.getByRole('button').isChecked();
 | 
			
		||||
    })().catch(e => e);
 | 
			
		||||
    const recorderPage = await recorderPageGetter();
 | 
			
		||||
    await recorderPage.click('[title="Resume (F8)"]');
 | 
			
		||||
    await recorderPage.waitForSelector('.source-line-error');
 | 
			
		||||
    expect(await sanitizeLog(recorderPage)).toEqual([
 | 
			
		||||
      'page.pause- XXms',
 | 
			
		||||
      'page.isChecked(button)- XXms',
 | 
			
		||||
      'waiting for selector "button"',
 | 
			
		||||
      'page.getByRole(\'button\').isChecked()- XXms',
 | 
			
		||||
      'waiting for \"getByRole(\'button\')"',
 | 
			
		||||
      'selector resolved to <button onclick=\"console.log(1)\">Submit</button>',
 | 
			
		||||
      'error: Error: Not a checkbox or radio button',
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
@ -74,7 +74,7 @@ test.describe('toBeChecked', () => {
 | 
			
		||||
    const locator2 = page.locator('input2');
 | 
			
		||||
    const error = await expect(locator2).not.toBeChecked({ timeout: 1000 }).catch(e => e);
 | 
			
		||||
    expect(error.message).toContain(`expect.toBeChecked with timeout 1000ms`);
 | 
			
		||||
    expect(error.message).toContain('waiting for selector "input2"');
 | 
			
		||||
    expect(error.message).toContain('waiting for "locator(\'input2\')"');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -152,7 +152,7 @@ test.describe('not.toHaveText', () => {
 | 
			
		||||
    const error = await expect(page.locator('span')).not.toHaveText('hello', { timeout: 1000 }).catch(e => e);
 | 
			
		||||
    expect(stripAnsi(error.message)).toContain('Expected string: not "hello"');
 | 
			
		||||
    expect(stripAnsi(error.message)).toContain('Received string: ""');
 | 
			
		||||
    expect(stripAnsi(error.message)).toContain('waiting for selector "span"');
 | 
			
		||||
    expect(stripAnsi(error.message)).toContain('waiting for "locator(\'span\')"');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -217,7 +217,7 @@ test.describe('toHaveText with array', () => {
 | 
			
		||||
    const error = await expect(locator).toHaveText(['Text 1', /Text \d/, 'Extra'], { timeout: 1000 }).catch(e => e);
 | 
			
		||||
    expect(stripAnsi(error.message)).toContain('-   "Extra"');
 | 
			
		||||
    expect(error.message).toContain('expect.toHaveText with timeout 1000ms');
 | 
			
		||||
    expect(error.message).toContain('waiting for selector "div"');
 | 
			
		||||
    expect(error.message).toContain('waiting for "locator(\'div\')"');
 | 
			
		||||
    expect(error.message).toContain('selector resolved to 2 elements');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -85,7 +85,7 @@ it('innerText should produce log', async ({ page, server }) => {
 | 
			
		||||
  await page.setContent(`<div>Hello</div>`);
 | 
			
		||||
  const locator = page.locator('span');
 | 
			
		||||
  const error = await locator.innerText({ timeout: 1000 }).catch(e => e);
 | 
			
		||||
  expect(error.message).toContain('waiting for selector "span"');
 | 
			
		||||
  expect(error.message).toContain('waiting for "locator(\'span\')"');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it('textContent should work', async ({ page, server }) => {
 | 
			
		||||
 | 
			
		||||
@ -81,8 +81,8 @@ it('elementHandle.waitForSelector should throw on navigation', async ({ page, se
 | 
			
		||||
    await page.evaluate(() => 1);
 | 
			
		||||
  await page.goto(server.EMPTY_PAGE);
 | 
			
		||||
  const error = await promise;
 | 
			
		||||
  expect(error.message).toContain('Error: frame navigated while waiting for selector');
 | 
			
		||||
  expect(error.message).toContain('span');
 | 
			
		||||
  expect(error.message).toContain('Error: frame navigated while waiting for');
 | 
			
		||||
  expect(error.message).toContain('"locator(\'span\')"');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it('should work with removed MutationObserver', async ({ page, server }) => {
 | 
			
		||||
@ -134,7 +134,7 @@ it('should report logs while waiting for visible', async ({ page, server }) => {
 | 
			
		||||
 | 
			
		||||
  const error = await watchdog.catch(e => e);
 | 
			
		||||
  expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
 | 
			
		||||
  expect(error.message).toContain(`waiting for selector "div" to be visible`);
 | 
			
		||||
  expect(error.message).toContain(`waiting for "locator(\'div\')" to be visible`);
 | 
			
		||||
  expect(error.message).toContain(`selector resolved to hidden <div id="mydiv" class="foo bar" foo="1234567890123456…>abcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvw…</div>`);
 | 
			
		||||
  expect(error.message).toContain(`selector did not resolve to any element`);
 | 
			
		||||
  expect(error.message).toContain(`selector resolved to hidden <div class="another"></div>`);
 | 
			
		||||
@ -165,7 +165,7 @@ it('should report logs while waiting for hidden', async ({ page, server }) => {
 | 
			
		||||
 | 
			
		||||
  const error = await watchdog.catch(e => e);
 | 
			
		||||
  expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
 | 
			
		||||
  expect(error.message).toContain(`waiting for selector "div" to be hidden`);
 | 
			
		||||
  expect(error.message).toContain(`waiting for "locator(\'div\')" to be hidden`);
 | 
			
		||||
  expect(error.message).toContain(`selector resolved to visible <div id="mydiv" class="foo bar">hello</div>`);
 | 
			
		||||
  expect(error.message).toContain(`selector resolved to visible <div class="another">hello</div>`);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -131,7 +131,7 @@ it('should respect timeout', async ({ page, playwright }) => {
 | 
			
		||||
  await page.waitForSelector('div', { timeout: 3000, state: 'attached' }).catch(e => error = e);
 | 
			
		||||
  expect(error).toBeTruthy();
 | 
			
		||||
  expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
 | 
			
		||||
  expect(error.message).toContain('waiting for selector "div"');
 | 
			
		||||
  expect(error.message).toContain('waiting for "locator(\'div\')"');
 | 
			
		||||
  expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -141,7 +141,7 @@ it('should have an error message specifically for awaiting an element to be hidd
 | 
			
		||||
  await page.waitForSelector('div', { state: 'hidden', timeout: 1000 }).catch(e => error = e);
 | 
			
		||||
  expect(error).toBeTruthy();
 | 
			
		||||
  expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded');
 | 
			
		||||
  expect(error.message).toContain('waiting for selector "div" to be hidden');
 | 
			
		||||
  expect(error.message).toContain('waiting for "locator(\'div\')" to be hidden');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it('should respond to node attribute mutation', async ({ page, server }) => {
 | 
			
		||||
@ -233,7 +233,7 @@ it('should respect timeout xpath', async ({ page, playwright }) => {
 | 
			
		||||
  await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e);
 | 
			
		||||
  expect(error).toBeTruthy();
 | 
			
		||||
  expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
 | 
			
		||||
  expect(error.message).toContain('waiting for selector "//div"');
 | 
			
		||||
  expect(error.message).toContain('waiting for "locator(\'xpath=//div\')"');
 | 
			
		||||
  expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -326,6 +326,6 @@ it('should fail when navigating while on handle', async ({ page, mode, server })
 | 
			
		||||
 | 
			
		||||
  const body = await page.waitForSelector('body');
 | 
			
		||||
  const error = await body.waitForSelector('div', { __testHookBeforeAdoptNode } as any).catch(e => e);
 | 
			
		||||
  expect(error.message).toContain('Error: frame navigated while waiting for selector');
 | 
			
		||||
  expect(error.message).toContain('"div"');
 | 
			
		||||
  expect(error.message).toContain('Error: frame navigated while waiting for');
 | 
			
		||||
  expect(error.message).toContain('"locator(\'div\')"');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -507,7 +507,7 @@ test('should print pending operations for toHaveText', async ({ runInlineTest })
 | 
			
		||||
  expect(output).toContain('Error: expect(received).toHaveText(expected)');
 | 
			
		||||
  expect(output).toContain('Expected string: "Text"');
 | 
			
		||||
  expect(output).toContain('Received string: ""');
 | 
			
		||||
  expect(output).toContain('waiting for selector "no-such-thing"');
 | 
			
		||||
  expect(output).toContain('waiting for "locator(\'no-such-thing\')"');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should print expected/received on Ctrl+C', async ({ runInlineTest }) => {
 | 
			
		||||
 | 
			
		||||
@ -329,8 +329,8 @@ test('should report error and pending operations on timeout', async ({ runInline
 | 
			
		||||
      test('timedout', async ({ page }) => {
 | 
			
		||||
        await page.setContent('<div>Click me</div>');
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
          page.click('text=Missing'),
 | 
			
		||||
          page.textContent('text=More missing'),
 | 
			
		||||
          page.getByText('Missing').click(),
 | 
			
		||||
          page.getByText('More missing').textContent(),
 | 
			
		||||
        ]);
 | 
			
		||||
      });
 | 
			
		||||
    `,
 | 
			
		||||
@ -340,10 +340,10 @@ test('should report error and pending operations on timeout', async ({ runInline
 | 
			
		||||
  expect(result.passed).toBe(0);
 | 
			
		||||
  expect(result.failed).toBe(1);
 | 
			
		||||
  expect(result.output).toContain('Pending operations:');
 | 
			
		||||
  expect(result.output).toContain('- page.click at a.test.ts:9:16');
 | 
			
		||||
  expect(result.output).toContain('- page.textContent at a.test.ts:10:16');
 | 
			
		||||
  expect(result.output).toContain('waiting for selector');
 | 
			
		||||
  expect(stripAnsi(result.output)).toContain(`10 |           page.textContent('text=More missing'),`);
 | 
			
		||||
  expect(result.output).toContain('- locator.click at a.test.ts:9:37');
 | 
			
		||||
  expect(result.output).toContain('- locator.textContent at a.test.ts:10:42');
 | 
			
		||||
  expect(result.output).toContain('waiting for');
 | 
			
		||||
  expect(stripAnsi(result.output)).toContain(`10 |           page.getByText('More missing').textContent(),`);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should report error on timeout with shared page', async ({ runInlineTest }, testInfo) => {
 | 
			
		||||
@ -358,7 +358,7 @@ test('should report error on timeout with shared page', async ({ runInlineTest }
 | 
			
		||||
        await page.setContent('<div>Click me</div>');
 | 
			
		||||
      });
 | 
			
		||||
      test('timedout', async () => {
 | 
			
		||||
        await page.click('text=Missing');
 | 
			
		||||
        await page.getByText('Missing').click();
 | 
			
		||||
      });
 | 
			
		||||
    `,
 | 
			
		||||
  }, { workers: 1, timeout: 2000 });
 | 
			
		||||
@ -366,8 +366,8 @@ test('should report error on timeout with shared page', async ({ runInlineTest }
 | 
			
		||||
  expect(result.exitCode).toBe(1);
 | 
			
		||||
  expect(result.passed).toBe(1);
 | 
			
		||||
  expect(result.failed).toBe(1);
 | 
			
		||||
  expect(result.output).toContain('waiting for selector "text=Missing"');
 | 
			
		||||
  expect(stripAnsi(result.output)).toContain(`14 |         await page.click('text=Missing');`);
 | 
			
		||||
  expect(result.output).toContain('waiting for "getByText(\'Missing\')"');
 | 
			
		||||
  expect(stripAnsi(result.output)).toContain(`14 |         await page.getByText('Missing').click();`);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should report error from beforeAll timeout', async ({ runInlineTest }, testInfo) => {
 | 
			
		||||
@ -378,8 +378,8 @@ test('should report error from beforeAll timeout', async ({ runInlineTest }, tes
 | 
			
		||||
        const page = await browser.newPage();
 | 
			
		||||
        await page.setContent('<div>Click me</div>');
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
          page.click('text=Missing'),
 | 
			
		||||
          page.textContent('text=More missing'),
 | 
			
		||||
          page.getByText('Missing').click(),
 | 
			
		||||
          page.getByText('More missing').textContent(),
 | 
			
		||||
        ]);
 | 
			
		||||
      });
 | 
			
		||||
      test('ignored', () => {});
 | 
			
		||||
@ -390,8 +390,8 @@ test('should report error from beforeAll timeout', async ({ runInlineTest }, tes
 | 
			
		||||
  expect(result.passed).toBe(0);
 | 
			
		||||
  expect(result.failed).toBe(1);
 | 
			
		||||
  expect(result.output).toContain('"beforeAll" hook timeout of 2000ms exceeded.');
 | 
			
		||||
  expect(result.output).toContain('waiting for selector');
 | 
			
		||||
  expect(stripAnsi(result.output)).toContain(`11 |           page.textContent('text=More missing'),`);
 | 
			
		||||
  expect(result.output).toContain('waiting for');
 | 
			
		||||
  expect(stripAnsi(result.output)).toContain(`11 |           page.getByText('More missing').textContent(),`);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should not report waitForEventInfo as pending', async ({ runInlineTest }, testInfo) => {
 | 
			
		||||
 | 
			
		||||
@ -394,7 +394,7 @@ test('should report api step failure', async ({ runInlineTest }) => {
 | 
			
		||||
    `%% begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
 | 
			
		||||
    `%% end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
 | 
			
		||||
    `%% begin {\"title\":\"page.click(input)\",\"category\":\"pw:api\"}`,
 | 
			
		||||
    `%% end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"page.click: Timeout 1ms exceeded.\\n=========================== logs ===========================\\nwaiting for selector \\\"input\\\"\\n============================================================\",\"stack\":\"<stack>\"}}`,
 | 
			
		||||
    `%% end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"page.click: Timeout 1ms exceeded.\\n=========================== logs ===========================\\nwaiting for \\\"locator('input')\\\"\\n============================================================\",\"stack\":\"<stack>\"}}`,
 | 
			
		||||
    `%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
 | 
			
		||||
    `%% begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
 | 
			
		||||
    `%% end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,12 @@ const ts = require('typescript');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
 | 
			
		||||
const packagesDir = path.normalize(path.join(__dirname, '..', 'packages'));
 | 
			
		||||
const packages = fs.readdirSync(packagesDir);
 | 
			
		||||
 | 
			
		||||
const packages = new Map();
 | 
			
		||||
for (const package of fs.readdirSync(packagesDir))
 | 
			
		||||
  packages.set(package, packagesDir + '/' + package + '/src/');
 | 
			
		||||
packages.set('isomorphic', packagesDir + '/playwright-core/src/server/isomorphic/');
 | 
			
		||||
 | 
			
		||||
const peerDependencies = ['electron', 'react', 'react-dom', '@zip.js/zip.js'];
 | 
			
		||||
 | 
			
		||||
const depsCache = {};
 | 
			
		||||
@ -106,8 +111,8 @@ async function innerCheckDeps(root, checkDepsFile, checkPackageJson) {
 | 
			
		||||
      } else if (importName.startsWith('@')) {
 | 
			
		||||
        const tokens = importName.substring(1).split('/');
 | 
			
		||||
        const package = tokens[0];
 | 
			
		||||
        if (packages.includes(package))
 | 
			
		||||
          importPath = packagesDir + '/' + tokens[0] + '/src/' + tokens.slice(1).join('/');
 | 
			
		||||
        if (packages.has(package))
 | 
			
		||||
          importPath = packages.get(package) + tokens.slice(1).join('/');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (importPath) {
 | 
			
		||||
@ -161,7 +166,7 @@ async function innerCheckDeps(root, checkDepsFile, checkPackageJson) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (line.startsWith('@'))
 | 
			
		||||
          group.push(line.replace(/@([\w-]+)\/(.*)/, path.join(packagesDir, '$1', 'src', '$2')));
 | 
			
		||||
          group.push(line.replace(/@([\w-]+)\/(.*)/, (_, arg1, arg2) => packages.get(arg1) + arg2));
 | 
			
		||||
        else
 | 
			
		||||
          group.push(path.resolve(depsDirectory, line));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user