Skip to content
2 changes: 1 addition & 1 deletion docs/src/api/class-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -2758,7 +2758,7 @@ Returns the opener for popup pages and `null` for others. If the opener has been
## async method: Page.pause
* since: v1.9

Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume'
Pauses script execution. Playwright will stop executing the script and wait for the user to either press the 'Resume'
button in the page overlay or to call `playwright.resume()` in the DevTools console.

User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from
Expand Down
4 changes: 3 additions & 1 deletion packages/injected/src/consoleApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ declare global {
interface Window {
playwright?: any;
inspect: (element: Element | undefined) => void;
__pw_resume: () => Promise<void>;
__pw_resume?: () => Promise<void>;
}
}

Expand Down Expand Up @@ -139,6 +139,8 @@ export class ConsoleAPI {
}

private _resume() {
if (!this._injectedScript.window.__pw_resume)
return false;
this._injectedScript.window.__pw_resume().catch(() => {});
}
}
4 changes: 2 additions & 2 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3599,8 +3599,8 @@ export interface Page {
opener(): Promise<null|Page>;

/**
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume'
* button in the page overlay or to call `playwright.resume()` in the DevTools console.
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press the
* 'Resume' button in the page overlay or to call `playwright.resume()` in the DevTools console.
*
* User can inspect selectors or perform manual steps while paused. Resume will continue running the original script
* from the place it was paused.
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export abstract class BrowserType extends SdkObject {
private _validateLaunchOptions(options: types.LaunchOptions): types.LaunchOptions {
const { devtools = false } = options;
let { headless = !devtools, downloadsPath, proxy } = options;
if (debugMode())
if (debugMode() === 'inspector')
headless = false;
if (downloadsPath && !path.isAbsolute(downloadsPath))
downloadsPath = path.join(process.cwd(), downloadsPath);
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/chromium/chromium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class Chromium extends BrowserType {
constructor(parent: SdkObject) {
super(parent, 'chromium');

if (debugMode())
if (debugMode() === 'inspector')
this._devtools = this._createDevTools();
}

Expand Down
6 changes: 3 additions & 3 deletions packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1628,11 +1628,11 @@ export class Frame extends SdkObject {
this._firedNetworkIdleSelf = false;
}

async extendInjectedScript(source: string, arg?: any): Promise<js.JSHandle> {
async extendInjectedScript(source: string, arg?: any) {
const context = await this._context('main');
const injectedScriptHandle = await context.injectedScript();
return injectedScriptHandle.evaluateHandle((injectedScript, { source, arg }) => {
return injectedScript.extend(source, arg);
await injectedScriptHandle.evaluate((injectedScript, { source, arg }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was leaking the handle.

injectedScript.extend(source, arg);
}, { source, arg });
}

Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/utils/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function debugMode() {
return 'console';
if (_debugMode === '0' || _debugMode === 'false')
return '';
return _debugMode ? 'inspector' : '';
return _debugMode ? 'inspector' : 'console';
}

const _isUnderTest = getAsBooleanFromENV('PWTEST_UNDER_TEST');
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/utils/nodePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const nodePlatform: Platform = {

inspectCustom: util.inspect.custom,

isDebugMode: () => !!debugMode(),
isDebugMode: () => debugMode() === 'inspector',

isJSDebuggerAttached: () => !!require('inspector').url(),

Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3599,8 +3599,8 @@ export interface Page {
opener(): Promise<null|Page>;

/**
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume'
* button in the page overlay or to call `playwright.resume()` in the DevTools console.
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press the
* 'Resume' button in the page overlay or to call `playwright.resume()` in the DevTools console.
*
* User can inspect selectors or perform manual steps while paused. Resume will continue running the original script
* from the place it was paused.
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
if (testIdAttribute)
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
testInfo.snapshotSuffix = process.platform;
if (debugMode())
if (debugMode() === 'inspector')
(testInfo as TestInfoImpl)._setDebugMode();

playwright._defaultContextOptions = _combinedContextOptions;
Expand Down
3 changes: 1 addition & 2 deletions tests/library/defaultbrowsercontext-2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,6 @@ it('coverage should work', async ({ server, launchPersistent, browserName }) =>
});

it('should respect selectors', async ({ playwright, launchPersistent }) => {
const { page } = await launchPersistent();

const defaultContextCSS = () => ({
query(root, selector) {
return root.querySelector(selector);
Expand All @@ -221,6 +219,7 @@ it('should respect selectors', async ({ playwright, launchPersistent }) => {
});
await playwright.selectors.register('defaultContextCSS', defaultContextCSS);

const { page } = await launchPersistent();
await page.setContent(`<div>hello</div>`);
expect(await page.innerHTML('css=div')).toBe('hello');
expect(await page.innerHTML('defaultContextCSS=div')).toBe('hello');
Expand Down
6 changes: 1 addition & 5 deletions tests/library/inspector/pause.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ it.describe('pause', () => {
// @ts-ignore
await page.pause({ __testHookKeepTestTimeout: true });
})();
await Promise.all([
page.waitForFunction(() => (window as any).playwright && (window as any).playwright.resume).then(() => {
return page.evaluate('window.playwright.resume()');
})
]);
await page.waitForFunction(() => (window as any).playwright?.resume() !== false);
await scriptPromise;
});

Expand Down
9 changes: 4 additions & 5 deletions tests/library/selectors-register.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,14 @@ it('should work when registered on global', async ({ browser, mode }) => {
});

it('should work with path', async ({ playwright, browser, asset }) => {
const page = await browser.newPage();
await playwright.selectors.register('foo', { path: asset('sectionselectorengine.js') });
const page = await browser.newPage();
await page.setContent('<section></section>');
expect(await page.$eval('foo=whatever', e => e.nodeName)).toBe('SECTION');
await page.close();
});

it('should work in main and isolated world', async ({ playwright, browser }) => {
const page = await browser.newPage();
const createDummySelector = () => ({
query(root, selector) {
return window['__answer'];
Expand All @@ -95,6 +94,7 @@ it('should work in main and isolated world', async ({ playwright, browser }) =>
});
await playwright.selectors.register('main', createDummySelector);
await playwright.selectors.register('isolated', createDummySelector, { contentScript: true });
const page = await browser.newPage();
await page.setContent('<div><span><section></section></span></div>');
await page.evaluate(() => window['__answer'] = document.querySelector('span'));
// Works in main if asked.
Expand Down Expand Up @@ -151,7 +151,6 @@ it('should throw "already registered" error when registering', { annotation: { t
});

it('should not rely on engines working from the root', async ({ playwright, browser }) => {
const page = await browser.newPage();
const createValueEngine = () => ({
query(root, selector) {
return root && root.value.includes(selector) ? root : undefined;
Expand All @@ -160,15 +159,14 @@ it('should not rely on engines working from the root', async ({ playwright, brow
return root && root.value.includes(selector) ? [root] : [];
},
});

await playwright.selectors.register('__value', createValueEngine);
const page = await browser.newPage();
await page.setContent(`<input id=input1 value=value1><input id=input2 value=value2>`);
expect(await page.$eval('input >> __value=value2', e => e.id)).toBe('input2');
await page.close();
});

it('should throw a nice error if the selector returns a bad value', async ({ playwright, browser }) => {
const page = await browser.newPage();
const createFakeEngine = () => ({
query(root, selector) {
return [document.body];
Expand All @@ -179,6 +177,7 @@ it('should throw a nice error if the selector returns a bad value', async ({ pla
});

await playwright.selectors.register('__fake', createFakeEngine);
const page = await browser.newPage();
const error = await page.$('__fake=value2').catch(e => e);
expect(error.message).toContain('Expected a Node but got [object Array]');
await page.close();
Expand Down
17 changes: 0 additions & 17 deletions tests/page/page-add-init-script.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,3 @@ it('init script should run only once in iframe', async ({ page, server, browserN
'init script: ' + (browserName === 'firefox' ? 'no url yet' : '/frames/frame.html'),
]);
});

it('init script should not observe playwright internals', async ({ server, page, trace, isAndroid }) => {
it.skip(!!process.env.PW_CLOCK, 'clock installs globalThis.__pwClock');
it.skip(trace === 'on', 'tracing installs __playwright_snapshot_streamer');
it.fixme(isAndroid, 'There is probably context reuse between this test and some other test that installs a binding');

await page.addInitScript(() => {
window['check'] = () => {
const keys = Reflect.ownKeys(globalThis).map(k => k.toString());
return keys.find(name => name.includes('playwright') || name.includes('_pw')) || 'none';
};
window['found'] = window['check']();
});
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate(() => window['found'])).toBe('none');
expect(await page.evaluate(() => window['check']())).toBe('none');
});
2 changes: 1 addition & 1 deletion tests/page/page-dispatchevent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ it('should be atomic', async ({ playwright, page }) => {
}
});
await playwright.selectors.register('dispatchEvent', createDummySelector);
await page.setContent(`<div onclick="window._clicked=true">Hello</div>`);
await page.goto(`data:text/html,<div onclick="window._clicked=true">Hello</div>`);
await page.dispatchEvent('dispatchEvent=div', 'click');
expect(await page.evaluate(() => window['_clicked'])).toBe(true);
});
Expand Down
12 changes: 6 additions & 6 deletions tests/page/selectors-register.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ it('textContent should be atomic', async ({ playwright, page }) => {
}
});
await playwright.selectors.register('textContent', createDummySelector);
await page.setContent(`<div>Hello</div>`);
await page.goto(`data:text/html,<div>Hello</div>`);
const tc = await page.textContent('textContent=div');
expect(tc).toBe('Hello');
expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('modified');
Expand All @@ -57,7 +57,7 @@ it('innerText should be atomic', async ({ playwright, page }) => {
}
});
await playwright.selectors.register('innerText', createDummySelector);
await page.setContent(`<div>Hello</div>`);
await page.goto(`data:text/html,<div>Hello</div>`);
const tc = await page.innerText('innerText=div');
expect(tc).toBe('Hello');
expect(await page.evaluate(() => document.querySelector('div').innerText)).toBe('modified');
Expand All @@ -79,7 +79,7 @@ it('innerHTML should be atomic', async ({ playwright, page }) => {
}
});
await playwright.selectors.register('innerHTML', createDummySelector);
await page.setContent(`<div>Hello<span>world</span></div>`);
await page.goto(`data:text/html,<div>Hello<span>world</span></div>`);
const tc = await page.innerHTML('innerHTML=div');
expect(tc).toBe('Hello<span>world</span>');
expect(await page.evaluate(() => document.querySelector('div').innerHTML)).toBe('modified');
Expand All @@ -101,7 +101,7 @@ it('getAttribute should be atomic', async ({ playwright, page }) => {
}
});
await playwright.selectors.register('getAttribute', createDummySelector);
await page.setContent(`<div foo=hello></div>`);
await page.goto(`data:text/html,<div foo=hello></div>`);
const tc = await page.getAttribute('getAttribute=div', 'foo');
expect(tc).toBe('hello');
expect(await page.evaluate(() => document.querySelector('div').getAttribute('foo'))).toBe('modified');
Expand All @@ -123,7 +123,7 @@ it('isVisible should be atomic', async ({ playwright, page }) => {
}
});
await playwright.selectors.register('isVisible', createDummySelector);
await page.setContent(`<div>Hello</div>`);
await page.goto(`data:text/html,<div>Hello</div>`);
const result = await page.isVisible('isVisible=div');
expect(result).toBe(true);
expect(await page.evaluate(() => document.querySelector('div').style.display)).toBe('none');
Expand All @@ -139,6 +139,6 @@ it('should take java-style string', async ({ playwright, page }) => {
}
}`;
await playwright.selectors.register('objectLiteral', createDummySelector);
await page.setContent(`<div>Hello</div>`);
await page.goto(`data:text/html,<div>Hello</div>`);
await page.textContent('objectLiteral=div');
});
57 changes: 55 additions & 2 deletions tests/playwright-test/playwright.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ test('page.pause() should disable test timeout', async ({ runInlineTest }) => {
expect(result.output).toContain('success!');
});

test('PWDEBUG=console should expose window.playwright', async ({ runInlineTest }) => {
test('window.playwright should be exposed by default', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36772' } }, async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
Expand All @@ -907,7 +907,60 @@ test('PWDEBUG=console should expose window.playwright', async ({ runInlineTest }
expect(bodyTag).toBe('BODY');
});
`,
}, {}, { PWDEBUG: 'console' });
}, {}, {});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});

test('window.playwright should not override existing property', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36772' } }, async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
await page.setContent('<script>window.playwright = "foo"</script>');
expect(await page.evaluate(() => window.playwright)).toBe('foo');
});
`,
}, {}, {});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});

test('PWDEBUG=0 should opt-out from exposing window.playwright', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36772' } }, async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
await page.setContent('<body></body>');
expect(await page.evaluate(() => window.playwright)).toBeUndefined();
});
`,
}, {}, { PWDEBUG: '0' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});

test('init script should not observe playwright internals', async ({ server, runInlineTest }) => {
test.skip(!!process.env.PW_CLOCK, 'clock installs globalThis.__pwClock');
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
await page.addInitScript(() => {
window['check'] = () => {
const keys = Reflect.ownKeys(globalThis).map(k => k.toString());
return keys.find(name => name.includes('playwright') || name.includes('_pw')) || 'none';
};
window['found'] = window['check']();
});
await page.goto("${server.EMPTY_PAGE}");
expect(await page.evaluate(() => window['found'])).toBe('none');
expect(await page.evaluate(() => window['check']())).toBe('none');
});
`,
}, {}, { PWDEBUG: '0' });
expect(result.exitCode).toBe(0);
});
20 changes: 11 additions & 9 deletions tests/playwright-test/test-step.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1449,20 +1449,20 @@ fixture | context

test('reading network request / response should not be listed as step', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33558' }
}, async ({ runInlineTest, server }) => {
}, async ({ runInlineTest, server, page }) => {
const result = await runInlineTest({
'reporter.ts': stepIndentReporter,
'playwright.config.ts': `module.exports = { reporter: './reporter' };`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('waitForResponse step nesting', async ({ page }) => {
page.on('request', async request => {
await request.allHeaders();
});
page.on('response', async response => {
await response.text();
});
await page.goto('${server.EMPTY_PAGE}');
const [request, response] = await Promise.all([
page.waitForRequest('${server.EMPTY_PAGE}'),
page.waitForResponse('${server.EMPTY_PAGE}'),
page.goto('${server.EMPTY_PAGE}'),
]);
await request.allHeaders();
await response.text();
});
`
}, { reporter: '', workers: 1, timeout: 3000 });
Expand All @@ -1476,7 +1476,9 @@ fixture | context
pw:api | Create context
fixture | page
pw:api | Create page
pw:api |Navigate to "/empty.html" @ a.test.ts:10
pw:api |Wait for event "request" @ a.test.ts:5
pw:api |Wait for event "response" @ a.test.ts:6
pw:api |Navigate to "/empty.html" @ a.test.ts:7
hook |After Hooks
fixture | page
fixture | context
Expand Down
Loading