Skip to content

Commit e5d201b

Browse files
committed
cherry-pick(#29031): fix(ct): allow passing date, url, bigint as properties
1 parent 8ee2d81 commit e5d201b

File tree

7 files changed

+126
-61
lines changed

7 files changed

+126
-61
lines changed

packages/playwright-ct-core/src/injected/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*/
1616

1717
import { ImportRegistry } from './importRegistry';
18-
import { unwrapObject } from './serializers';
18+
import { transformObject, unwrapObject } from './serializers';
1919

2020
window.__pwRegistry = new ImportRegistry();
2121
window.__pwUnwrapObject = unwrapObject;
22+
window.__pwTransformObject = transformObject;

packages/playwright-ct-core/src/injected/serializers.ts

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,48 +26,68 @@ function isFunctionRef(value: any): value is FunctionRef {
2626
}
2727

2828
export function wrapObject(value: any, callbacks: Function[]): any {
29-
if (typeof value === 'function') {
30-
const ordinal = callbacks.length;
31-
callbacks.push(value as Function);
32-
const result: FunctionRef = {
33-
__pw_type: 'function',
34-
ordinal,
35-
};
36-
return result;
37-
}
29+
return transformObject(value, (v: any) => {
30+
if (typeof v === 'function') {
31+
const ordinal = callbacks.length;
32+
callbacks.push(v as Function);
33+
const result: FunctionRef = {
34+
__pw_type: 'function',
35+
ordinal,
36+
};
37+
return { result };
38+
}
39+
});
40+
}
41+
42+
export async function unwrapObject(value: any): Promise<any> {
43+
return transformObjectAsync(value, async (v: any) => {
44+
if (isFunctionRef(v)) {
45+
const result = (...args: any[]) => {
46+
window.__ctDispatchFunction(v.ordinal, args);
47+
};
48+
return { result };
49+
}
50+
if (isImportRef(v))
51+
return { result: await window.__pwRegistry.resolveImportRef(v) };
52+
});
53+
}
54+
55+
export function transformObject(value: any, mapping: (v: any) => { result: any } | undefined): any {
56+
const result = mapping(value);
57+
if (result)
58+
return result.result;
3859
if (value === null || typeof value !== 'object')
3960
return value;
61+
if (value instanceof Date || value instanceof RegExp || value instanceof URL)
62+
return value;
4063
if (Array.isArray(value)) {
4164
const result = [];
4265
for (const item of value)
43-
result.push(wrapObject(item, callbacks));
66+
result.push(transformObject(item, mapping));
4467
return result;
4568
}
46-
const result: any = {};
69+
const result2: any = {};
4770
for (const [key, prop] of Object.entries(value))
48-
result[key] = wrapObject(prop, callbacks);
49-
return result;
71+
result2[key] = transformObject(prop, mapping);
72+
return result2;
5073
}
5174

52-
export async function unwrapObject(value: any): Promise<any> {
75+
export async function transformObjectAsync(value: any, mapping: (v: any) => Promise<{ result: any } | undefined>): Promise<any> {
76+
const result = await mapping(value);
77+
if (result)
78+
return result.result;
5379
if (value === null || typeof value !== 'object')
5480
return value;
55-
if (isFunctionRef(value)) {
56-
return (...args: any[]) => {
57-
window.__ctDispatchFunction(value.ordinal, args);
58-
};
59-
}
60-
if (isImportRef(value))
61-
return window.__pwRegistry.resolveImportRef(value);
62-
81+
if (value instanceof Date || value instanceof RegExp || value instanceof URL)
82+
return value;
6383
if (Array.isArray(value)) {
6484
const result = [];
6585
for (const item of value)
66-
result.push(await unwrapObject(item));
86+
result.push(await transformObjectAsync(item, mapping));
6787
return result;
6888
}
69-
const result: any = {};
89+
const result2: any = {};
7090
for (const [key, prop] of Object.entries(value))
71-
result[key] = await unwrapObject(prop);
72-
return result;
91+
result2[key] = await transformObjectAsync(prop, mapping);
92+
return result2;
7393
}

packages/playwright-ct-core/types/component.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ declare global {
5959
// Can't start with __pw due to core reuse bindings logic for __pw*.
6060
__ctDispatchFunction: (ordinal: number, args: any[]) => void;
6161
__pwUnwrapObject: (value: any) => Promise<any>;
62+
__pwTransformObject: (value: any, mapping: (v: any) => { result: any } | undefined) => any;
6263
}
6364
}

packages/playwright-ct-react/registerSource.mjs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,13 @@ function isJsxComponent(component) {
3636
* @param {any} value
3737
*/
3838
function __pwRender(value) {
39-
if (value === null || typeof value !== 'object')
40-
return value;
41-
if (isJsxComponent(value)) {
42-
const component = value;
43-
const props = component.props ? __pwRender(component.props) : {};
44-
return __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children);
45-
}
46-
if (Array.isArray(value)) {
47-
const result = [];
48-
for (const item of value)
49-
result.push(__pwRender(item));
50-
return result;
51-
}
52-
const result = {};
53-
for (const [key, prop] of Object.entries(value))
54-
result[key] = __pwRender(prop);
55-
return result;
39+
return window.__pwTransformObject(value, v => {
40+
if (isJsxComponent(v)) {
41+
const component = v;
42+
const props = component.props ? __pwRender(component.props) : {};
43+
return { result: __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children) };
44+
}
45+
});
5646
}
5747

5848
window.playwrightMount = async (component, rootElement, hooksConfig) => {

packages/playwright-ct-react17/registerSource.mjs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,13 @@ function isJsxComponent(component) {
3333
* @param {any} value
3434
*/
3535
function __pwRender(value) {
36-
if (value === null || typeof value !== 'object')
37-
return value;
38-
if (isJsxComponent(value)) {
39-
const component = value;
40-
const props = component.props ? __pwRender(component.props) : {};
41-
return __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children);
42-
}
43-
if (Array.isArray(value)) {
44-
const result = [];
45-
for (const item of value)
46-
result.push(__pwRender(item));
47-
return result;
48-
}
49-
const result = {};
50-
for (const [key, prop] of Object.entries(value))
51-
result[key] = __pwRender(prop);
52-
return result;
36+
return window.__pwTransformObject(value, v => {
37+
if (isJsxComponent(v)) {
38+
const component = v;
39+
const props = component.props ? __pwRender(component.props) : {};
40+
return { result: __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children) };
41+
}
42+
});
5343
}
5444

5545
window.playwrightMount = async (component, rootElement, hooksConfig) => {

tests/playwright-test/playwright.ct-build.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,3 +551,65 @@ test('should pass imported images from test to component', async ({ runInlineTes
551551
expect(result.exitCode).toBe(0);
552552
expect(result.passed).toBe(1);
553553
});
554+
555+
test('should pass dates, regex, urls and bigints', async ({ runInlineTest }) => {
556+
const result = await runInlineTest({
557+
'playwright.config.ts': playwrightConfig,
558+
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
559+
'playwright/index.ts': ``,
560+
'src/button.tsx': `
561+
export const Button = ({ props }: any) => {
562+
const { date, url, bigint, regex } = props;
563+
const types = [
564+
date instanceof Date,
565+
url instanceof URL,
566+
typeof bigint === 'bigint',
567+
regex instanceof RegExp,
568+
];
569+
return <div>{types.join(' ')}</div>;
570+
};
571+
`,
572+
'src/component.spec.tsx': `
573+
import { test, expect } from '@playwright/experimental-ct-react';
574+
import { Button } from './button';
575+
576+
test('renders props with builtin types', async ({ mount, page }) => {
577+
const component = await mount(<Button props={{
578+
date: new Date(),
579+
url: new URL('https://example.com'),
580+
bigint: BigInt(42),
581+
regex: /foo/,
582+
}} />);
583+
await expect(component).toHaveText('true true true true');
584+
});
585+
`,
586+
}, { workers: 1 });
587+
588+
expect(result.exitCode).toBe(0);
589+
expect(result.passed).toBe(1);
590+
});
591+
592+
test('should pass undefined value as param', async ({ runInlineTest }) => {
593+
const result = await runInlineTest({
594+
'playwright.config.ts': playwrightConfig,
595+
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
596+
'playwright/index.ts': ``,
597+
'src/component.tsx': `
598+
export const Component = ({ value }: { value?: number }) => {
599+
return <div>{typeof value}</div>;
600+
};
601+
`,
602+
'src/component.spec.tsx': `
603+
import { test, expect } from '@playwright/experimental-ct-react';
604+
import { Component } from './component';
605+
606+
test('renders props with undefined type', async ({ mount, page }) => {
607+
const component = await mount(<Component value={undefined} />);
608+
await expect(component).toHaveText('undefined');
609+
});
610+
`,
611+
}, { workers: 1 });
612+
613+
expect(result.exitCode).toBe(0);
614+
expect(result.passed).toBe(1);
615+
});

utils/build/build.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ steps.push({
304304
onChanges.push({
305305
inputs: [
306306
'packages/playwright-core/src/server/injected/**',
307+
'packages/playwright-ct-core/src/injected/**',
307308
'packages/playwright-core/src/utils/isomorphic/**',
308309
'utils/generate_injected.js',
309310
],

0 commit comments

Comments
 (0)