Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🎭 Playwright

[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-146.0.7680.31-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-146.0.1-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-26.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord)
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-146.0.7680.31-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-148.0.2-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-26.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord)

## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)

Expand All @@ -10,7 +10,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
| :--- | :---: | :---: | :---: |
| Chromium<sup>1</sup> <!-- GEN:chromium-version -->146.0.7680.31<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->146.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.

Expand Down
8 changes: 4 additions & 4 deletions packages/playwright-core/browsers.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@
},
{
"name": "firefox",
"revision": "1509",
"revision": "1511",
"installByDefault": true,
"browserVersion": "146.0.1",
"browserVersion": "148.0.2",
"title": "Firefox"
},
{
"name": "firefox-beta",
"revision": "1504",
"revision": "1505",
"installByDefault": false,
"browserVersion": "146.0b8",
"browserVersion": "148.0b9",
"title": "Firefox Beta"
},
{
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/remote/playwrightConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export class PlaywrightConnection {
}

private async _onDisconnect(error?: Error) {
if (this._disconnected)
return;
this._disconnected = true;
debugLogger.log('server', `[${this._id}] disconnected. error: ${error}`);
await this._root.stopPendingOperations(new Error('Disconnected')).catch(() => {});
Expand Down
25 changes: 14 additions & 11 deletions packages/playwright-core/src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import fs from 'fs';
import path from 'path';

import { createGuid } from './utils/crypto';
import { debugMode } from './utils/debug';
import { debugMode, isUnderTest } from './utils/debug';
import { Clock } from './clock';
import { Debugger } from './debugger';
import { DialogManager } from './dialog';
Expand Down Expand Up @@ -149,18 +149,21 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk
// Debugger will pause execution upon page.pause in headed mode.
this._debugger = new Debugger(this);

// When PWDEBUG=1, show inspector for each context.
if (debugMode() === 'inspector')
await RecorderApp.show(this, { pauseOnNextStatement: true });

// When paused, show inspector.
if (this._debugger.isPaused())
RecorderApp.showInspectorNoReply(this);
const shouldEnableDebugger = !this.attribution.playwright.options.isServer && (isUnderTest() || !!this._browser.options.headful);
if (shouldEnableDebugger) {
this._debugger.setPauseAt();
this._debugger.on(Debugger.Events.PausedStateChanged, () => {
if (this._debugger.isPaused())
RecorderApp.showInspectorNoReply(this);
});
}

this._debugger.on(Debugger.Events.PausedStateChanged, () => {
if (this._debugger.isPaused())
RecorderApp.showInspectorNoReply(this);
});
// When PWDEBUG=1, show inspector for each context.
if (debugMode() === 'inspector') {
this._debugger.setPauseAt({ next: true });
await RecorderApp.show(this, { pauseOnNextStatement: true });
}

if (debugMode() === 'console')
await this.exposeConsoleApi();
Expand Down
7 changes: 2 additions & 5 deletions packages/playwright-core/src/server/debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { SdkObject } from './instrumentation';
import { debugMode, isUnderTest, monotonicTime } from '../utils';
import { monotonicTime } from '../utils';
import { BrowserContext } from './browserContext';
import { methodMetainfo } from '../utils/isomorphic/protocolMetainfo';

Expand All @@ -28,7 +28,7 @@ type PauseAt = { next?: boolean, location?: { file: string, line?: number, colum
export class Debugger extends SdkObject implements InstrumentationListener {
private _pauseAt: PauseAt = {};
private _pausedCallsMetadata = new Map<CallMetadata, { resolve: () => void, sdkObject: SdkObject }>();
private _enabled: boolean;
private _enabled = false;
private _context: BrowserContext;

static Events = {
Expand All @@ -40,9 +40,6 @@ export class Debugger extends SdkObject implements InstrumentationListener {
super(context, 'debugger');
this._context = context;
(this._context as any)[symbol] = this;
this._enabled = !context.attribution.playwright.options.isServer && (isUnderTest() || !!context._browser.options.headful);
if (debugMode() === 'inspector')
this.setPauseAt({ next: true });
context.instrumentation.addListener(this, context);
this._context.once(BrowserContext.Events.Close, () => {
this._context.instrumentation.removeListener(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1702,7 +1702,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0.1) Gecko/20100101 Firefox/146.0.1",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0.2) Gecko/20100101 Firefox/148.0.2",
"screen": {
"width": 1792,
"height": 1120
Expand Down Expand Up @@ -1762,7 +1762,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0.1) Gecko/20100101 Firefox/146.0.1",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0.2) Gecko/20100101 Firefox/148.0.2",
"screen": {
"width": 1920,
"height": 1080
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/electron/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ export class Electron extends SdkObject {

async launch(progress: Progress, options: Omit<channels.ElectronLaunchParams, 'timeout'>): Promise<ElectronApplication> {
let app: ElectronApplication | undefined = undefined;
// --inspect=0 must be the last playwright's argument, loader.ts relies on it.
let electronArguments = ['--inspect=0', ...(options.args || [])];
// --remote-debugging-port=0 must be the last playwright's argument, loader.ts relies on it.
let electronArguments = ['--inspect=0', '--remote-debugging-port=0', ...(options.args || [])];

if (os.platform() === 'linux') {
if (!options.chromiumSandbox && electronArguments.indexOf('--no-sandbox') === -1)
Expand Down
4 changes: 1 addition & 3 deletions packages/playwright-core/src/server/electron/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ const { chromiumSwitches } = require('../chromium/chromiumSwitches');
// Always pass user arguments first, see https://github.com/microsoft/playwright/issues/16614 and
// https://github.com/microsoft/playwright/issues/29198.
// [Electron, -r, loader.js[, --no-sandbox>], --inspect=0, --remote-debugging-port=0, ...args]
process.argv.splice(1, process.argv.indexOf('--inspect=0'));

app.commandLine.appendSwitch('remote-debugging-port', '0');
process.argv.splice(1, process.argv.indexOf('--remote-debugging-port=0'));

for (const arg of chromiumSwitches()) {
const match = arg.match(/--([^=]*)=?(.*)/)!;
Expand Down
32 changes: 23 additions & 9 deletions packages/playwright-core/src/tools/cli-client/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import crypto from 'crypto';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { createClientInfo, Registry, resolveSessionName } from './registry';
import { createClientInfo, explicitSessionName, Registry, resolveSessionName } from './registry';
import { Session, renderResolvedConfig } from './session';
import { serverRegistry } from '../../serverRegistry';

Expand All @@ -43,6 +43,7 @@ type GlobalOptions = {
};

type OpenOptions = {
attach?: string;
browser?: string;
config?: string;
extension?: boolean;
Expand All @@ -52,6 +53,7 @@ type OpenOptions = {
};

const globalOptions: (keyof (GlobalOptions & OpenOptions))[] = [
'attach',
'browser',
'config',
'extension',
Expand Down Expand Up @@ -148,13 +150,15 @@ export async function program(options?: { embedderVersion?: string}) {
return;
}
case 'open': {
const entry = registry.entry(clientInfo, sessionName);
if (entry)
await new Session(entry).stop(true);

await Session.startDaemon(clientInfo, args);
const newEntry = await registry.loadEntry(clientInfo, sessionName);
await runInSession(newEntry, clientInfo, args);
await startSession(sessionName, registry, clientInfo, args);
return;
}
case 'attach': {
const attachTarget = args._[1];
const attachSessionName = explicitSessionName(args.session) ?? attachTarget;
args.attach = attachTarget;
args.session = attachSessionName;
await startSession(attachSessionName, registry, clientInfo, args);
return;
}
case 'close':
Expand Down Expand Up @@ -194,6 +198,16 @@ export async function program(options?: { embedderVersion?: string}) {
}
}

async function startSession(sessionName: string, registry: Registry, clientInfo: ClientInfo, args: MinimistArgs) {
const entry = registry.entry(clientInfo, sessionName);
if (entry)
await new Session(entry).stop(true);

await Session.startDaemon(clientInfo, args);
const newEntry = await registry.loadEntry(clientInfo, sessionName);
await runInSession(newEntry, clientInfo, args);
}

async function runInSession(entry: SessionFile, clientInfo: ClientInfo, args: MinimistArgs) {
for (const globalOption of globalOptions)
delete args[globalOption];
Expand Down Expand Up @@ -413,7 +427,7 @@ async function gcAndPrintBrowserSessions(workspace: string, list: BrowserDescrip
text.push(`- browser "${descriptor.title}":`);
text.push(` - browser: ${descriptor.browser.browserName}`);
text.push(` - version: v${descriptor.playwrightVersion}`);
text.push(` - run \`playwright-cli open --attach "${descriptor.title}"\` to attach`);
text.push(` - run \`playwright-cli attach "${descriptor.title}"\` to attach`);
console.log(text.join('\n'));
}

Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/tools/cli-client/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ const daemonProfilesDir = (workspaceDirHash: string) => {
return path.join(baseDaemonDir, workspaceDirHash);
};

export function explicitSessionName(sessionName?: string): string | undefined {
return sessionName || process.env.PLAYWRIGHT_CLI_SESSION;
}

export function resolveSessionName(sessionName?: string): string {
if (sessionName)
return sessionName;
Expand Down
7 changes: 6 additions & 1 deletion packages/playwright-core/src/tools/cli-client/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,12 @@ export class Session {
child.stdout!.destroy();
child.unref();

console.log(`### Browser \`${sessionName}\` opened with pid ${child.pid}.`);
if (cliArgs['attach']) {
console.log(`### Session \`${sessionName}\` created, attached to \`${cliArgs['attach']}\`.`);
console.log(`Run commands with: playwright --session=${sessionName} <command>`);
} else {
console.log(`### Browser \`${sessionName}\` opened with pid ${child.pid}.`);
}
}

private async _stopDaemon(): Promise<void> {
Expand Down
17 changes: 16 additions & 1 deletion packages/playwright-core/src/tools/cli-daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,26 @@ const open = declareCommand({
headed: z.boolean().optional().describe('Run browser in headed mode'),
persistent: z.boolean().optional().describe('Use persistent browser profile'),
profile: z.string().optional().describe('Use persistent browser profile, store profile in specified directory.'),
attach: z.string().optional().describe('Attach to a running Playwright browser by name or endpoint'),
}),
toolName: ({ url }) => url ? 'browser_navigate' : 'browser_snapshot',
toolParams: ({ url }) => ({ url: url || 'about:blank' }),
});

const attach = declareCommand({
name: 'attach',
description: 'Attach to a running Playwright browser',
category: 'core',
args: z.object({
name: z.string().describe('Name or endpoint of the browser to attach to'),
}),
options: z.object({
config: z.string().optional().describe('Path to the configuration file, defaults to .playwright/cli.config.json'),
session: z.string().optional().describe('Session name alias (defaults to the attach target name)'),
}),
toolName: 'browser_snapshot',
toolParams: () => ({}),
});

const close = declareCommand({
name: 'close',
description: 'Close the browser',
Expand Down Expand Up @@ -888,6 +902,7 @@ const tray = declareCommand({
const commandsArray: AnyCommandSchema[] = [
// core category
open,
attach,
close,
goto,
type,
Expand Down
16 changes: 14 additions & 2 deletions packages/playwright/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
testInfo.snapshotSuffix = process.platform;
testInfo._onCustomMessageCallback = () => Promise.reject(new Error('Only tests that use default Playwright context or page fixture support test_debug'));
if (debugMode() === 'inspector')
(testInfo as TestInfoImpl)._setDebugMode();
(testInfo as TestInfoImpl)._setIgnoreTimeouts(true);

playwright._defaultContextTimeout = actionTimeout || 0;
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
Expand All @@ -256,6 +256,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);

const tracingGroupSteps: TestStepInternal[] = [];
const pausedContexts = new Set<BrowserContextImpl>();
const csiListener: ClientInstrumentationListener = {
onApiCallBegin: (data, channel) => {
const testInfo = currentTestInfo();
Expand Down Expand Up @@ -304,7 +305,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
},
onWillPause: ({ keepTestTimeout }) => {
if (!keepTestTimeout)
currentTestInfo()?._setDebugMode();
currentTestInfo()?._setIgnoreTimeouts(true);
},
runBeforeCreateBrowserContext: async (options: BrowserContextOptions) => {
for (const [key, value] of Object.entries(_combinedContextOptions)) {
Expand All @@ -319,6 +320,17 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
}
},
runAfterCreateBrowserContext: async (context: BrowserContextImpl) => {
context.debugger.on('pausedstatechanged', () => {
const paused = context.debugger.pausedDetails().length > 0;
if (pausedContexts.has(context) && !paused) {
pausedContexts.delete(context);
(testInfo as TestInfoImpl)._setIgnoreTimeouts(false);
} else if (!pausedContexts.has(context) && paused) {
pausedContexts.add(context);
(testInfo as TestInfoImpl)._setIgnoreTimeouts(true);
}
});

await artifactsRecorder.didCreateBrowserContext(context);
const testInfo = currentTestInfo();
if (testInfo)
Expand Down
11 changes: 5 additions & 6 deletions packages/playwright/src/isomorphic/testServerConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ import type * as reporterTypes from '../../types/testReporter';
// -- Reuse boundary -- Everything below this line is reused in the vscode extension.

export class TestServerConnectionClosedError extends Error {
constructor() {
super('Test server connection closed');
}
}

export interface TestServerTransport {
Expand Down Expand Up @@ -86,7 +83,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte

private _lastId = 0;
private _transport: TestServerTransport;
private _callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void }>();
private _callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void, error: TestServerConnectionClosedError }>();
private _connectedPromise: Promise<void>;
private _isClosed = false;

Expand Down Expand Up @@ -125,7 +122,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._onCloseEmitter.fire();
clearInterval(pingInterval);
for (const callback of this._callbacks.values())
callback.reject(new TestServerConnectionClosedError());
callback.reject(callback.error);
this._callbacks.clear();
});
}
Expand All @@ -141,9 +138,11 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
await this._connectedPromise;
const id = ++this._lastId;
const message = { id, method, params };
// Capture proper stack trace in the error here.
const error = new TestServerConnectionClosedError(`${method}: test server connection closed`);
this._transport.send(JSON.stringify(message));
return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject });
this._callbacks.set(id, { resolve, reject, error });
});
}

Expand Down
Loading
Loading