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
15 changes: 13 additions & 2 deletions .github/workflows/tests_secondary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,19 @@ jobs:
strategy:
fail-fast: false
matrix:
channel: [chrome, chrome-beta, msedge, msedge-beta, msedge-dev]
runs-on: [ubuntu-22.04, macos-latest, windows-latest]
include:
- channel: chrome
runs-on: ubuntu-22.04
- channel: chrome
runs-on: macos-latest
- channel: chrome
runs-on: windows-latest
- channel: chrome-beta
runs-on: ubuntu-22.04
- channel: msedge
runs-on: windows-latest
- channel: msedge-dev
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/run-test
Expand Down
38 changes: 0 additions & 38 deletions .github/workflows/tests_video.yml

This file was deleted.

2 changes: 1 addition & 1 deletion docs/src/test-cli-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ npx playwright test --ui
| `--fully-parallel` | Run all tests in parallel (default: false). |
| `--global-timeout <timeout>` | Maximum time this test suite can run in milliseconds (default: unlimited). |
| `-g <grep>` or `--grep <grep>` | Only run tests matching this regular expression (default: ".*"). |
| `--grep-invert <grep>` | Only run tests that do not match this regular expression. |
| `-G <grep>` or `--grep-invert <grep>` | Only run tests that do not match this regular expression. |
| `--headed` | Run tests in headed browsers (default: headless). |
| `--ignore-snapshots` | Ignore screenshot and snapshot expectations. |
| `-j <workers>` or `--workers <workers>` | Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%). |
Expand Down
16 changes: 15 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"@types/source-map-support": "^0.5.4",
"@types/ws": "8.18.1",
"@types/xml2js": "^0.4.9",
"@types/yauzl": "^2.10.3",
"@types/yazl": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^8.59.0",
"@typescript-eslint/parser": "^8.59.0",
Expand Down Expand Up @@ -162,6 +163,7 @@
"ws": "8.17.1",
"xml2js": "^0.5.0",
"yaml": "^2.8.3",
"yauzl": "3.3.2",
"yazl": "2.5.1",
"zod": "^4.3.6",
"zod-to-json-schema": "^3.25.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/browsers.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
},
{
"name": "webkit",
"revision": "2299",
"revision": "2300",
"installByDefault": true,
"revisionOverrides": {
"mac14": "2251",
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/cli/installActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ export async function markDockerImage(dockerImageNameTemplate: string) {
await writeDockerVersion(dockerImageNameTemplate);
}

export async function installBrowsers(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean }) {
export async function installBrowsers(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean, progress?: boolean }) {
if (options.progress === false)
process.env.PLAYWRIGHT_DOWNLOAD_NO_PROGRESS = '1';
if (isLikelyNpxGlobal()) {
console.error(wrapInASCIIBox([
`WARNING: It looks like you are running 'npx playwright install' without first`,
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export function decorateProgram(program: Command) {
.option('--force', 'force reinstall of already installed browsers')
.option('--only-shell', 'only install headless shell when installing chromium')
.option('--no-shell', 'do not install chromium headless shell')
.action(async function(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean }) {
.option('--no-progress', 'do not show download progress bars')
.action(async function(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean, progress?: boolean }) {
try {
await installBrowsers(args, options);
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/server/DEPS.list
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ node_modules/pngjs
node_modules/proxy-from-env
node_modules/ws
node_modules/yaml
node_modules/yauzl
node_modules/yazl

[devtoolsController.ts]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ class BidiRequest {
const postDataBuffer = null;
this.request = new network.Request(frame._page.browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigation ?? undefined,
payload.request.url, resourceTypeFromBidi(payload.request.destination, payload.request.initiatorType, payload.initiator?.type), payload.request.method,
postDataBuffer, headersOverride || fromBidiHeaders(payload.request.headers));
postDataBuffer, headersOverride || fromBidiHeaders(payload.request.headers), payload.timestamp);
// "raw" headers are the same as "provisional" headers in Bidi.
this.request.setRawRequestHeaders(null);
this.request._setBodySize(payload.request.bodySize || 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class CRNetworkManager {
if (this._page) {
sessionInfo.eventListeners.push(...[
eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page!.frameManager.onWebSocketCreated(e.requestId, e.url)),
eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers, '\n'), e.wallTime, e.timestamp)),
eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers, '\n'), e.wallTime * 1000, e.timestamp)),
eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page!.frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText, headersObjectToArray(e.response.headers, '\n'))),
eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page!.frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page!.frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)),
Expand Down Expand Up @@ -605,7 +605,7 @@ class InterceptableRequest {
if (entries && entries.length)
postDataBuffer = Buffer.concat(entries.map(entry => Buffer.from(entry.bytes!, 'base64')));

this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, toResourceType(requestWillBeSentEvent.type || 'Other'), method, postDataBuffer, headersOverride || headersObjectToArray(headers));
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, toResourceType(requestWillBeSentEvent.type || 'Other'), method, postDataBuffer, headersOverride || headersObjectToArray(headers), requestWillBeSentEvent.wallTime * 1000);
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ export class FFPage implements PageDelegate {
}

_onWebSocketFrameReceived(event: Protocol.Page.webSocketFrameReceivedPayload) {
this._page.frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp);
this._page.frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp * 1000);
}

_onWebSocketFrameSent(event: Protocol.Page.webSocketFrameSentPayload) {
this._page.frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp);
this._page.frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp * 1000);
}

_onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) {
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,12 @@ export class FrameManager {
if (!ws)
return;

ws.setRequestTiming(wallTime, timestamp);

if (ws.markAsNotified())
this._page.emit(Page.Events.WebSocket, ws);

ws.requestSent(headers, wallTime, timestamp);
ws.requestSent(headers);
}

onWebSocketResponse(requestId: string, status: number, statusText: string, headers: types.HeadersArray) {
Expand Down
14 changes: 10 additions & 4 deletions packages/playwright-core/src/server/har/harTracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export class HarTracer {
return;

const pageEntry = this._createPageEntryIfNeeded(page);
const harEntry = createHarEntry(pageEntry?.id, request.method(), url, request.frame()?.guid, this._options);
const harEntry = createHarEntry(pageEntry?.id, request.method(), url, request.frame()?.guid, this._options, request.wallTimeMs());
harEntry._resourceType = request.resourceType();
this._recordRequestHeadersAndCookies(harEntry, request.headers());
harEntry.request.postData = this._postDataForRequest(request, this._options.content);
Expand Down Expand Up @@ -441,7 +441,7 @@ export class HarTracer {
return;

const pageEntry = this._createPageEntryIfNeeded(page);
const harEntry = createHarEntry(pageEntry?.id, 'GET', url, page.mainFrame().guid, this._options);
const harEntry = createHarEntry(pageEntry?.id, 'GET', url, page.mainFrame().guid, this._options, webSocket.wallTimeMs());
harEntry._resourceType = 'websocket';
harEntry._webSocketMessages = [];

Expand Down Expand Up @@ -535,6 +535,11 @@ export class HarTracer {
const timing = response.timing();
if (pageEntry && startDateTime > timing.startTime)
pageEntry.startedDateTime = new Date(timing.startTime).toISOString();
if (request.wallTimeMs() === undefined && timing.startTime > 0) {
const startedDateTime = safeDateToISOString(timing.startTime);
if (startedDateTime)
harEntry.startedDateTime = startedDateTime;
}
const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1;
const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1;
const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1;
Expand Down Expand Up @@ -670,10 +675,11 @@ export class HarTracer {

}

function createHarEntry(pageRef: string | undefined, method: string, url: URL, frameref: string | undefined, options: HarTracerOptions): har.Entry {
function createHarEntry(pageRef: string | undefined, method: string, url: URL, frameref: string | undefined, options: HarTracerOptions, wallTime?: number): har.Entry {
const startedDateTime = (wallTime && safeDateToISOString(wallTime)) || new Date().toISOString();
const harEntry: har.Entry = {
pageref: pageRef,
startedDateTime: new Date().toISOString(),
startedDateTime,
time: -1,
request: {
method: method,
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/localUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import fs from 'fs';
import os from 'os';
import path from 'path';

import * as yauzl from 'yauzl';
import * as yazl from 'yazl';
import * as yauzl from '@utils/third_party/yauzl';
import { ManualPromise } from '@isomorphic/manualPromise';
import { serializeClientSideCallMetadata } from '@isomorphic/trace/traceUtils';
import { assert } from '@isomorphic/assert';
Expand Down
24 changes: 18 additions & 6 deletions packages/playwright-core/src/server/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,15 @@ export class Request extends SdkObject {
_responseEndTiming = -1;
private _overrides: NormalizedContinueOverrides | undefined;
private _bodySize: number | undefined;
private _wallTimeMs: number | undefined;
_responseBodyOverride: { body: string; isBase64: boolean; } | undefined;

static Events = {
Response: 'response',
};

constructor(context: contexts.BrowserContext, frame: frames.Frame | null, serviceWorker: pages.Worker | null, redirectedFrom: Request | null, documentId: string | undefined,
url: string, resourceType: ResourceType, method: string, postData: Buffer | null, headers: HeadersArray) {
url: string, resourceType: ResourceType, method: string, postData: Buffer | null, headers: HeadersArray, wallTimeMs?: number) {
super(frame || context, 'request');
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
this._context = context;
Expand All @@ -211,9 +212,14 @@ export class Request extends SdkObject {
this._method = method;
this._postData = postData;
this._headers = headers;
this._wallTimeMs = wallTimeMs;
this._isFavicon = url.endsWith('/favicon.ico') || !!redirectedFrom?._isFavicon;
}

wallTimeMs(): number | undefined {
return this._wallTimeMs;
}

async raceWithPageClosure<T>(progress: Progress, promise: Promise<T>): Promise<T> {
const scope = this._serviceWorker?.openScope ?? this._frame?._page.openScope;
if (scope)
Expand Down Expand Up @@ -725,7 +731,7 @@ export class Response extends SdkObject {
export class WebSocket extends SdkObject {
private _url: string;
private _notified = false;
private _requestWallTime: number | undefined;
private _requestWallTimeMs: number | undefined;
private _requestTimestamp: number | undefined;
private _status: number | undefined;
private _statusText: string | undefined;
Expand Down Expand Up @@ -760,10 +766,16 @@ export class WebSocket extends SdkObject {
return this._url;
}

requestSent(headers: HeadersArray, wallTime?: number, timestamp?: number) {
this._requestWallTime = wallTime;
wallTimeMs(): number | undefined {
return this._requestWallTimeMs;
}

setRequestTiming(wallTimeMs: number | undefined, timestamp: number | undefined) {
this._requestWallTimeMs = wallTimeMs;
this._requestTimestamp = timestamp;
}

requestSent(headers: HeadersArray) {
this.emit(WebSocket.Events.Request, { headers });
}

Expand All @@ -773,8 +785,8 @@ export class WebSocket extends SdkObject {

private _toWallTime(timestamp: number): number {
// The timestamp of each frame is relative to the timestamp (and walltime) of the initial request in Chromium and WebKit.
if (this._requestWallTime !== undefined && this._requestTimestamp !== undefined)
return this._requestWallTime + (timestamp - this._requestTimestamp);
if (this._requestWallTimeMs !== undefined && this._requestTimestamp !== undefined)
return this._requestWallTimeMs + (timestamp - this._requestTimestamp);

// The timestamp is already a walltime in Firefox.
return timestamp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import fs from 'fs';
import path from 'path';

import { ManualPromise } from '@isomorphic/manualPromise';
import { getAsBooleanFromENV } from '@utils/env';
import { httpRequest } from '@utils/network';
import { removeFolders } from '@utils/fileUtils';
import { extractZip } from '@utils/third_party/extractZip';
Expand Down Expand Up @@ -48,6 +49,7 @@ function downloadFile(options: DownloadParams): Promise<void> {
let downloadedBytes = 0;
let totalBytes = 0;
let chunked = false;
const reportProgress = !getAsBooleanFromENV('PLAYWRIGHT_DOWNLOAD_NO_PROGRESS');

const promise = new ManualPromise<void>();
httpRequest({
Expand Down Expand Up @@ -106,7 +108,7 @@ function downloadFile(options: DownloadParams): Promise<void> {

function onData(chunk: string) {
downloadedBytes += chunk.length;
if (!chunked)
if (!chunked && reportProgress)
progress(downloadedBytes, totalBytes);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class WVInterceptableRequest {
postDataBuffer = Buffer.from(event.request.postData, 'utf8');
}
this.request = new network.Request(frame._page.browserContext, frame, null, redirectedFrom?.request || null, documentId, event.request.url,
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers));
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers), this._wallTime);
}

adoptRequestFromNewProcess(newSession: WVSession, requestId: string) {
Expand Down
Loading
Loading