Skip to content
This repository was archived by the owner on Oct 12, 2026. It is now read-only.
Open
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
33 changes: 33 additions & 0 deletions .github/workflows/cf_unit_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "Playwright for Cloudflare - Unit Tests"

on:
pull_request:
paths:
- "packages/playwright-cloudflare/src/**"
- "packages/playwright-cloudflare/tests/**"
- ".github/workflows/cf_unit_tests.yml"
push:
branches: [main]
paths:
- "packages/playwright-cloudflare/src/**"
- "packages/playwright-cloudflare/tests/**"
- ".github/workflows/cf_unit_tests.yml"

permissions:
contents: read

jobs:
unit-tests:
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/playwright-cloudflare/tests
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: npm
cache-dependency-path: packages/playwright-cloudflare/tests/package-lock.json
- run: npm ci
- run: npm run test:unit
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const transportZone = new AsyncLocalStorage<WebSocketTransport>();

export class WebSocketTransport implements ConnectionTransport {
private _ws: WebSocket;
private _closing = false;
onmessage?: (message: ProtocolResponse) => void;
onclose?: () => void;
readonly sessionId: string | undefined;
Expand All @@ -26,19 +27,26 @@ export class WebSocketTransport implements ConnectionTransport {
this.onmessage?.(JSON.parse(event.data) as ProtocolResponse);
});
this._ws.addEventListener('close', () => {
this._closing = true;
this.onclose?.();
});
this._ws.addEventListener('error', e => {
if (this._closing)
return;
// eslint-disable-next-line no-console
console.error(`Websocket error${sessionId ? `: SessionID: ${sessionId}` : ''}`, e);
});
}

send(message: ProtocolRequest): void {
// Browser Run can raise an error event while completing a requested shutdown.
if (message.method === 'Browser.close')
this._closing = true;
this._ws.send(JSON.stringify(message));
}

close(): void {
this._closing = true;
this._ws.close();
this.onclose?.();
}
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-cloudflare/tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"description": "",
"types": "./test-d/index.d.ts",
"scripts": {
"test:unit": "playwright test --config=unit/playwright.config.ts",
"test-types": "tsd"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: '.',
workers: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { test as base, expect } from '@playwright/test';

import { WebSocketTransport } from '../../src/cloudflare/webSocketTransport';

const test = base.extend<{ errors: unknown[][] }>({
errors: async ({}, use) => {
const errors: unknown[][] = [];
// eslint-disable-next-line no-console
const original = console.error;
// eslint-disable-next-line no-console
console.error = (...args: unknown[]) => errors.push(args);
try {
await use(errors);
} finally {
// eslint-disable-next-line no-console
console.error = original;
}
},
});

class MockWebSocket extends EventTarget {
sent: string[] = [];

send(message: string) {
this.sent.push(message);
this.dispatchEvent(new Event('error'));
}

close() {
this.dispatchEvent(new Event('error'));
}
}

test('reports errors while the browser session is active', async ({ errors }) => {
const ws = new MockWebSocket();
new WebSocketTransport(ws as unknown as WebSocket, 'session-id');
const error = new Event('error');

ws.dispatchEvent(error);

expect(errors).toEqual([['Websocket error: SessionID: session-id', error]]);
});

test('does not report errors raised while closing the transport', async ({ errors }) => {
const ws = new MockWebSocket();
const transport = new WebSocketTransport(ws as unknown as WebSocket, undefined);
let closed = false;
transport.onclose = () => closed = true;

transport.close();

expect(errors.length).toBe(0);
expect(closed).toBe(true);
});

test('does not report errors after sending Browser.close', async ({ errors }) => {
const ws = new MockWebSocket();
const transport = new WebSocketTransport(ws as unknown as WebSocket, 'session-id');
const message = { id: -9999, method: 'Browser.close', params: {} };

transport.send(message);
ws.dispatchEvent(new Event('error'));

expect(errors.length).toBe(0);
expect(ws.sent).toEqual([JSON.stringify(message)]);
});

test('reports errors when sending other protocol commands', async ({ errors }) => {
const ws = new MockWebSocket();
const transport = new WebSocketTransport(ws as unknown as WebSocket, undefined);

transport.send({ id: 1, method: 'Browser.getVersion', params: {} });

expect(errors).toHaveLength(1);
expect(errors[0][0]).toBe('Websocket error');
});

test('does not report errors after the websocket closes', async ({ errors }) => {
const ws = new MockWebSocket();
const transport = new WebSocketTransport(ws as unknown as WebSocket, 'session-id');
let closed = false;
transport.onclose = () => closed = true;

ws.dispatchEvent(new Event('close'));
ws.dispatchEvent(new Event('error'));

expect(errors.length).toBe(0);
expect(closed).toBe(true);
});