Skip to content
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
6 changes: 6 additions & 0 deletions packages/playground/src/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ function isLoopbackOrigin(origin?: string) {
return true;
}

// Local HTML reports opened through file:// have an opaque origin, which
// browsers serialize as "null" in CORS requests.
if (origin === 'null') {
return true;
}

try {
const url = new URL(origin);
return LOOPBACK_HOSTS.has(url.hostname);
Expand Down
54 changes: 54 additions & 0 deletions packages/playground/tests/unit/launcher.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'node:path';
import { describe, expect, it, rs } from '@rstest/core';
import cors, { type CorsOptions } from 'cors';
import {
playgroundForAgent,
playgroundForAgentFactory,
Expand All @@ -19,6 +20,44 @@ function createMockAgent() {

const staticPath = path.resolve(process.cwd(), 'static');

type CorsOriginResult =
| boolean
| string
| RegExp
| Array<boolean | string | RegExp>;

function checkCorsOrigin(options: CorsOptions, origin: string) {
return new Promise<CorsOriginResult>((resolve, reject) => {
if (typeof options.origin !== 'function') {
reject(new Error('Expected a dynamic CORS origin policy'));
return;
}

options.origin(origin, (error, allowed) => {
if (error) {
reject(error);
return;
}

if (allowed === undefined) {
reject(new Error('Expected the CORS origin policy to return a value'));
return;
}

resolve(allowed);
});
});
}

function getConfiguredCorsOptions(): CorsOptions {
const corsMock = cors as unknown as ReturnType<typeof rs.fn>;
const options = corsMock.mock.calls[0]?.[0];
if (!options) {
throw new Error('Expected CORS middleware to be configured');
}
return options;
}

describe('playground launcher', () => {
it('should build browser URLs for IPv4, hostnames, and IPv6 literals', () => {
expect(buildPlaygroundBrowserUrl('127.0.0.1', 5921)).toBe(
Expand Down Expand Up @@ -88,6 +127,21 @@ describe('playground launcher', () => {
}
});

it('should allow file protocol reports when CORS is enabled', async () => {
const result = await playgroundForAgent(createMockAgent()).launch({
port: 5925,
openBrowser: false,
verbose: false,
staticPath,
enableCors: true,
});

const corsOptions = getConfiguredCorsOptions();
await expect(checkCorsOrigin(corsOptions, 'null')).resolves.toBe(true);

await result.close();
});

it('should launch from agent factory and allow server configuration', async () => {
const agentFactory = rs.fn(async () => createMockAgent());
let configuredServer: any;
Expand Down
5 changes: 4 additions & 1 deletion packages/web-integration/src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ export const webPlaygroundPlatform = definePlaygroundPlatform<
title: options?.title || 'Midscene Web Playground',
agent: agent || (!agentFactory ? createDefaultWebAgent() : undefined),
agentFactory,
launchOptions: options?.launchOptions,
launchOptions: {
enableCors: true,
...options?.launchOptions,
},
preview:
options?.preview ||
createMjpegPreviewDescriptor({
Expand Down
3 changes: 3 additions & 0 deletions packages/web-integration/tests/unit-test/platform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('webPlaygroundPlatform', () => {
expect(prepared.agent).toBeUndefined();
expect(prepared.agentFactory).toBeTypeOf('function');
expect(createdAgent).toBeInstanceOf(StaticPageAgent);
expect(prepared.launchOptions).toMatchObject({ enableCors: true });
expect(prepared.preview).toMatchObject({
kind: 'mjpeg',
screenshotPath: '/screenshot',
Expand All @@ -24,13 +25,15 @@ describe('webPlaygroundPlatform', () => {
launchOptions: {
port: 5807,
openBrowser: true,
enableCors: false,
},
});

expect(prepared.title).toBe('Custom Web Playground');
expect(prepared.launchOptions).toMatchObject({
port: 5807,
openBrowser: true,
enableCors: false,
});
});
});
Loading