chore: parse tsx tests (#10917)
This commit is contained in:
parent
04e82ce71c
commit
f579f9c806
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -590,7 +590,6 @@
|
||||||
"version": "7.14.5",
|
"version": "7.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz",
|
||||||
"integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==",
|
"integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.14.5"
|
"@babel/helper-plugin-utils": "^7.14.5"
|
||||||
},
|
},
|
||||||
|
|
@ -731,7 +730,6 @@
|
||||||
"version": "7.14.5",
|
"version": "7.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.5.tgz",
|
||||||
"integrity": "sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q==",
|
"integrity": "sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-annotate-as-pure": "^7.14.5",
|
"@babel/helper-annotate-as-pure": "^7.14.5",
|
||||||
"@babel/helper-module-imports": "^7.14.5",
|
"@babel/helper-module-imports": "^7.14.5",
|
||||||
|
|
@ -9151,6 +9149,7 @@
|
||||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||||
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
|
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
|
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
|
||||||
|
"@babel/plugin-transform-react-jsx": "^7.14.5",
|
||||||
"@babel/preset-typescript": "^7.14.5",
|
"@babel/preset-typescript": "^7.14.5",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
|
|
@ -9574,7 +9573,6 @@
|
||||||
"version": "7.14.5",
|
"version": "7.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz",
|
||||||
"integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==",
|
"integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-plugin-utils": "^7.14.5"
|
"@babel/helper-plugin-utils": "^7.14.5"
|
||||||
}
|
}
|
||||||
|
|
@ -9667,7 +9665,6 @@
|
||||||
"version": "7.14.5",
|
"version": "7.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.5.tgz",
|
||||||
"integrity": "sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q==",
|
"integrity": "sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-annotate-as-pure": "^7.14.5",
|
"@babel/helper-annotate-as-pure": "^7.14.5",
|
||||||
"@babel/helper-module-imports": "^7.14.5",
|
"@babel/helper-module-imports": "^7.14.5",
|
||||||
|
|
@ -9966,6 +9963,7 @@
|
||||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||||
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
|
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
|
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
|
||||||
|
"@babel/plugin-transform-react-jsx": "^7.14.5",
|
||||||
"@babel/preset-typescript": "^7.14.5",
|
"@babel/preset-typescript": "^7.14.5",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"wtest": "playwright test --config=tests/config/default.config.ts --project=webkit",
|
"wtest": "playwright test --config=tests/config/default.config.ts --project=webkit",
|
||||||
"atest": "playwright test --config=tests/config/android.config.ts",
|
"atest": "playwright test --config=tests/config/android.config.ts",
|
||||||
"etest": "playwright test --config=tests/config/electron.config.ts",
|
"etest": "playwright test --config=tests/config/electron.config.ts",
|
||||||
"htest": "playwright test --config=packages/html-reporter",
|
"htest": "cross-env PW_COMPONENT_TESTING=1 playwright test --config=packages/html-reporter",
|
||||||
"ttest": "node ./tests/playwright-test/stable-test-runner/node_modules/@playwright/test/cli test --config=tests/playwright-test/playwright-test.config.ts",
|
"ttest": "node ./tests/playwright-test/stable-test-runner/node_modules/@playwright/test/cli test --config=tests/playwright-test/playwright-test.config.ts",
|
||||||
"vtest": "cross-env PLAYWRIGHT_DOCKER=1 node ./tests/playwright-test/stable-test-runner/node_modules/@playwright/test/cli test --config=tests/playwright-test/playwright-test.config.ts",
|
"vtest": "cross-env PLAYWRIGHT_DOCKER=1 node ./tests/playwright-test/stable-test-runner/node_modules/@playwright/test/cli test --config=tests/playwright-test/playwright-test.config.ts",
|
||||||
"test": "playwright test --config=tests/config/default.config.ts",
|
"test": "playwright test --config=tests/config/default.config.ts",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { AutoChip, Chip } from './src/chip';
|
||||||
|
import { HeaderView } from './src/headerView';
|
||||||
|
import { TestCaseView } from './src/testCaseView';
|
||||||
import './src/theme.css';
|
import './src/theme.css';
|
||||||
import './src/chip.story.tsx';
|
import { registerComponent } from './test/component';
|
||||||
import './src/headerView.story.tsx';
|
|
||||||
|
registerComponent('HeaderView', HeaderView);
|
||||||
|
registerComponent('Chip', Chip);
|
||||||
|
registerComponent('TestCaseView', TestCaseView);
|
||||||
|
registerComponent('AutoChip', AutoChip);
|
||||||
|
|
@ -14,12 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
import { test, expect } from '../test/componentTest';
|
import { test, expect } from '../test/componentTest';
|
||||||
|
import { Chip, AutoChip } from './chip';
|
||||||
|
|
||||||
test.use({ webpack: require.resolve('../webpack.config.js') });
|
test.use({ webpack: require.resolve('../webpack.config.js') });
|
||||||
|
test.use({ viewport: { width: 500, height: 500 } });
|
||||||
|
|
||||||
test('chip expand collapse', async ({ renderComponent }) => {
|
test('chip expand collapse', async ({ render }) => {
|
||||||
const component = await renderComponent('ChipComponent');
|
const component = await render(<AutoChip header='title'>
|
||||||
|
Chip body
|
||||||
|
</AutoChip>);
|
||||||
await expect(component.locator('text=Chip body')).toBeVisible();
|
await expect(component.locator('text=Chip body')).toBeVisible();
|
||||||
// expect(await component.screenshot()).toMatchSnapshot('expanded.png');
|
// expect(await component.screenshot()).toMatchSnapshot('expanded.png');
|
||||||
await component.locator('text=Title').click();
|
await component.locator('text=Title').click();
|
||||||
|
|
@ -30,18 +35,20 @@ test('chip expand collapse', async ({ renderComponent }) => {
|
||||||
// expect(await component.screenshot()).toMatchSnapshot('expanded.png');
|
// expect(await component.screenshot()).toMatchSnapshot('expanded.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('chip render long title', async ({ renderComponent }) => {
|
test('chip render long title', async ({ render }) => {
|
||||||
const title = 'Extremely long title. '.repeat(10);
|
const title = 'Extremely long title. '.repeat(10);
|
||||||
const component = await renderComponent('ChipComponent', { title });
|
const component = await render(<AutoChip header={title}>
|
||||||
|
Chip body
|
||||||
|
</AutoChip>);
|
||||||
await expect(component).toContainText('Extremely long title.');
|
await expect(component).toContainText('Extremely long title.');
|
||||||
await expect(component.locator('text=Extremely long title.')).toHaveAttribute('title', title);
|
await expect(component.locator('text=Extremely long title.')).toHaveAttribute('title', title);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('chip setExpanded is called', async ({ renderComponent }) => {
|
test('chip setExpanded is called', async ({ render }) => {
|
||||||
const expandedValues: boolean[] = [];
|
const expandedValues: boolean[] = [];
|
||||||
const component = await renderComponent('ChipComponentWithFunctions', {
|
const component = await render(<Chip header='Title'
|
||||||
setExpanded: (expanded: boolean) => expandedValues.push(expanded)
|
setExpanded={(expanded: boolean) => expandedValues.push(expanded)}>
|
||||||
});
|
</Chip>);
|
||||||
|
|
||||||
await component.locator('text=Title').click();
|
await component.locator('text=Title').click();
|
||||||
expect(expandedValues).toEqual([true]);
|
expect(expandedValues).toEqual([true]);
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Chip } from './chip';
|
|
||||||
import { registerComponent } from '../test/component';
|
|
||||||
|
|
||||||
const ChipComponent: React.FC<{
|
|
||||||
title?: string
|
|
||||||
}> = ({ title }) => {
|
|
||||||
const [expanded, setExpanded] = React.useState(true);
|
|
||||||
return <Chip header={title || 'Title'} expanded={expanded} setExpanded={setExpanded}>
|
|
||||||
Chip body
|
|
||||||
</Chip>;
|
|
||||||
};
|
|
||||||
registerComponent('ChipComponent', ChipComponent, {
|
|
||||||
viewport: { width: 500, height: 500 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const ChipComponentWithFunctions: React.FC<{
|
|
||||||
setExpanded: (expanded: boolean) => void,
|
|
||||||
}> = ({ setExpanded }) => {
|
|
||||||
return <Chip header='Title' expanded={false} setExpanded={setExpanded}>
|
|
||||||
Chip body
|
|
||||||
</Chip>;
|
|
||||||
};
|
|
||||||
registerComponent('ChipComponentWithFunctions', ChipComponentWithFunctions, {
|
|
||||||
viewport: { width: 500, height: 500 },
|
|
||||||
});
|
|
||||||
|
|
@ -39,3 +39,20 @@ export const Chip: React.FunctionComponent<{
|
||||||
{(!setExpanded || expanded) && <div className={'chip-body' + (noInsets ? ' chip-body-no-insets' : '')}>{children}</div>}
|
{(!setExpanded || expanded) && <div className={'chip-body' + (noInsets ? ' chip-body-no-insets' : '')}>{children}</div>}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AutoChip: React.FC<{
|
||||||
|
header: JSX.Element | string,
|
||||||
|
initialExpanded?: boolean,
|
||||||
|
noInsets?: boolean,
|
||||||
|
children?: any,
|
||||||
|
}> = ({ header, initialExpanded, noInsets, children }) => {
|
||||||
|
const [expanded, setExpanded] = React.useState(initialExpanded || initialExpanded === undefined);
|
||||||
|
return <Chip
|
||||||
|
header={header}
|
||||||
|
expanded={expanded}
|
||||||
|
setExpanded={setExpanded}
|
||||||
|
noInsets={noInsets}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Chip>;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,15 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Stats } from '@playwright/test/src/reporters/html';
|
import React from 'react';
|
||||||
import { test, expect } from '../test/componentTest';
|
import { test, expect } from '../test/componentTest';
|
||||||
|
import { HeaderView } from './headerView';
|
||||||
|
|
||||||
test.use({ webpack: require.resolve('../webpack.config.js') });
|
test.use({ webpack: require.resolve('../webpack.config.js') });
|
||||||
|
test.use({ viewport: { width: 720, height: 200 } });
|
||||||
|
|
||||||
test('should render counters', async ({ renderComponent }) => {
|
test('should render counters', async ({ render }) => {
|
||||||
const stats: Stats = {
|
const component = await render(<HeaderView stats={{
|
||||||
total: 100,
|
total: 100,
|
||||||
expected: 42,
|
expected: 42,
|
||||||
unexpected: 31,
|
unexpected: 31,
|
||||||
|
|
@ -28,8 +30,7 @@ test('should render counters', async ({ renderComponent }) => {
|
||||||
skipped: 10,
|
skipped: 10,
|
||||||
ok: false,
|
ok: false,
|
||||||
duration: 100000
|
duration: 100000
|
||||||
};
|
}} filterText='' setFilterText={() => {}}></HeaderView>);
|
||||||
const component = await renderComponent('HeaderView', { stats });
|
|
||||||
await expect(component.locator('a', { hasText: 'All' }).locator('.counter')).toHaveText('100');
|
await expect(component.locator('a', { hasText: 'All' }).locator('.counter')).toHaveText('100');
|
||||||
await expect(component.locator('a', { hasText: 'Passed' }).locator('.counter')).toHaveText('42');
|
await expect(component.locator('a', { hasText: 'Passed' }).locator('.counter')).toHaveText('42');
|
||||||
await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31');
|
await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31');
|
||||||
|
|
@ -37,21 +38,21 @@ test('should render counters', async ({ renderComponent }) => {
|
||||||
await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10');
|
await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should toggle filters', async ({ page, renderComponent }) => {
|
test('should toggle filters', async ({ page, render: render }) => {
|
||||||
const stats: Stats = {
|
|
||||||
total: 100,
|
|
||||||
expected: 42,
|
|
||||||
unexpected: 31,
|
|
||||||
flaky: 17,
|
|
||||||
skipped: 10,
|
|
||||||
ok: false,
|
|
||||||
duration: 100000
|
|
||||||
};
|
|
||||||
const filters: string[] = [];
|
const filters: string[] = [];
|
||||||
const component = await renderComponent('HeaderView', {
|
const component = await render(<HeaderView
|
||||||
stats,
|
stats={{
|
||||||
setFilterText: (filterText: string) => filters.push(filterText)
|
total: 100,
|
||||||
});
|
expected: 42,
|
||||||
|
unexpected: 31,
|
||||||
|
flaky: 17,
|
||||||
|
skipped: 10,
|
||||||
|
ok: false,
|
||||||
|
duration: 100000
|
||||||
|
}}
|
||||||
|
filterText=''
|
||||||
|
setFilterText={(filterText: string) => filters.push(filterText)}>
|
||||||
|
</HeaderView>);
|
||||||
await component.locator('a', { hasText: 'All' }).click();
|
await component.locator('a', { hasText: 'All' }).click();
|
||||||
await component.locator('a', { hasText: 'Passed' }).click();
|
await component.locator('a', { hasText: 'Passed' }).click();
|
||||||
await expect(page).toHaveURL(/#\?q=s:passed/);
|
await expect(page).toHaveURL(/#\?q=s:passed/);
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { registerComponent } from '../test/component';
|
|
||||||
import { HeaderView } from './headerView';
|
|
||||||
|
|
||||||
registerComponent('HeaderView', HeaderView, {
|
|
||||||
viewport: { width: 720, height: 500 },
|
|
||||||
});
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { HTMLReport, TestAttachment } from '@playwright/test/src/reporters/html';
|
import type { TestAttachment } from '@playwright/test/src/reporters/html';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as icons from './icons';
|
import * as icons from './icons';
|
||||||
import { TreeItem } from './treeItem';
|
import { TreeItem } from './treeItem';
|
||||||
|
|
@ -53,13 +53,13 @@ export const Link: React.FunctionComponent<{
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectLink: React.FunctionComponent<{
|
export const ProjectLink: React.FunctionComponent<{
|
||||||
report: HTMLReport,
|
projectNames: string[],
|
||||||
projectName: string,
|
projectName: string,
|
||||||
}> = ({ report, projectName }) => {
|
}> = ({ projectNames, projectName }) => {
|
||||||
const encoded = encodeURIComponent(projectName);
|
const encoded = encodeURIComponent(projectName);
|
||||||
const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
|
const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
|
||||||
return <Link href={`#?q=p:${value}`}>
|
return <Link href={`#?q=p:${value}`}>
|
||||||
<span className={'label label-color-' + (report.projectNames.indexOf(projectName) % 6)}>
|
<span className={'label label-color-' + (projectNames.indexOf(projectName) % 6)}>
|
||||||
{projectName}
|
{projectName}
|
||||||
</span>
|
</span>
|
||||||
</Link>;
|
</Link>;
|
||||||
|
|
|
||||||
|
|
@ -80,5 +80,5 @@ const TestCaseViewLoader: React.FC<{
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [test, report, testId]);
|
}, [test, report, testId]);
|
||||||
return <TestCaseView report={report.json()} test={test}></TestCaseView>;
|
return <TestCaseView projectNames={report.json().projectNames} test={test}></TestCaseView>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
73
packages/html-reporter/src/testCaseView.spec.tsx
Normal file
73
packages/html-reporter/src/testCaseView.spec.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { test, expect } from '../test/componentTest';
|
||||||
|
import { TestCaseView } from './testCaseView';
|
||||||
|
import type { TestCase, TestResult } from '../../playwright-test/src/reporters/html';
|
||||||
|
|
||||||
|
test.use({ webpack: require.resolve('../webpack.config.js') });
|
||||||
|
test.use({ viewport: { width: 800, height: 600 } });
|
||||||
|
|
||||||
|
const result: TestResult = {
|
||||||
|
retry: 0,
|
||||||
|
startTime: new Date(0).toUTCString(),
|
||||||
|
duration: 100,
|
||||||
|
steps: [{
|
||||||
|
title: 'Outer step',
|
||||||
|
startTime: new Date(100).toUTCString(),
|
||||||
|
duration: 10,
|
||||||
|
location: { file: 'test.spec.ts', line: 62, column: 0 },
|
||||||
|
steps: [{
|
||||||
|
title: 'Inner step',
|
||||||
|
startTime: new Date(200).toUTCString(),
|
||||||
|
duration: 10,
|
||||||
|
location: { file: 'test.spec.ts', line: 82, column: 0 },
|
||||||
|
steps: [],
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
attachments: [],
|
||||||
|
status: 'passed',
|
||||||
|
};
|
||||||
|
|
||||||
|
const testCase: TestCase = {
|
||||||
|
testId: 'testid',
|
||||||
|
title: 'My test',
|
||||||
|
path: [],
|
||||||
|
projectName: 'chromium',
|
||||||
|
location: { file: 'test.spec.ts', line: 42, column: 0 },
|
||||||
|
annotations: [
|
||||||
|
{ type: 'annotation', description: 'Annotation text' },
|
||||||
|
{ type: 'annotation', description: 'Another annotation text' },
|
||||||
|
],
|
||||||
|
outcome: 'expected',
|
||||||
|
duration: 10,
|
||||||
|
ok: true,
|
||||||
|
results: [result]
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should render counters', async ({ render }) => {
|
||||||
|
const component = await render(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase}></TestCaseView>);
|
||||||
|
await expect(component.locator('text=Annotation text').first()).toBeVisible();
|
||||||
|
await component.locator('text=Annotations').click();
|
||||||
|
await expect(component.locator('text=Annotation text')).not.toBeVisible();
|
||||||
|
await expect(component.locator('text=Outer step')).toBeVisible();
|
||||||
|
await expect(component.locator('text=Inner step')).not.toBeVisible();
|
||||||
|
await component.locator('text=Outer step').click();
|
||||||
|
await expect(component.locator('text=Inner step')).toBeVisible();
|
||||||
|
await expect(component.locator('text=test.spec.ts:42')).toBeVisible();
|
||||||
|
await expect(component.locator('text=My test')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { HTMLReport, TestCase } from '@playwright/test/src/reporters/html';
|
import type { TestCase } from '@playwright/test/src/reporters/html';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { TabbedPane } from './tabbedPane';
|
import { TabbedPane } from './tabbedPane';
|
||||||
import { Chip } from './chip';
|
import { AutoChip } from './chip';
|
||||||
import './common.css';
|
import './common.css';
|
||||||
import { ProjectLink } from './links';
|
import { ProjectLink } from './links';
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
|
|
@ -25,22 +25,22 @@ import './testCaseView.css';
|
||||||
import { TestResultView } from './testResultView';
|
import { TestResultView } from './testResultView';
|
||||||
|
|
||||||
export const TestCaseView: React.FC<{
|
export const TestCaseView: React.FC<{
|
||||||
report: HTMLReport,
|
projectNames: string[],
|
||||||
test: TestCase | undefined,
|
test: TestCase | undefined,
|
||||||
}> = ({ report, test }) => {
|
}> = ({ projectNames, test }) => {
|
||||||
const [selectedResultIndex, setSelectedResultIndex] = React.useState(0);
|
const [selectedResultIndex, setSelectedResultIndex] = React.useState(0);
|
||||||
|
|
||||||
return <div className='test-case-column vbox'>
|
return <div className='test-case-column vbox'>
|
||||||
{test && <div className='test-case-path'>{test.path.join(' › ')}</div>}
|
{test && <div className='test-case-path'>{test.path.join(' › ')}</div>}
|
||||||
{test && <div className='test-case-title'>{test?.title}</div>}
|
{test && <div className='test-case-title'>{test?.title}</div>}
|
||||||
{test && <div className='test-case-location'>{test.location.file}:{test.location.line}</div>}
|
{test && <div className='test-case-location'>{test.location.file}:{test.location.line}</div>}
|
||||||
{test && !!test.projectName && <ProjectLink report={report} projectName={test.projectName}></ProjectLink>}
|
{test && !!test.projectName && <ProjectLink projectNames={projectNames} projectName={test.projectName}></ProjectLink>}
|
||||||
{test && !!test.annotations.length && <Chip header='Annotations'>
|
{test && !!test.annotations.length && <AutoChip header='Annotations'>
|
||||||
{test.annotations.map(a => <div className='test-case-annotation'>
|
{test.annotations.map(a => <div className='test-case-annotation'>
|
||||||
<span style={{ fontWeight: 'bold' }}>{a.type}</span>
|
<span style={{ fontWeight: 'bold' }}>{a.type}</span>
|
||||||
{a.description && <span>: {a.description}</span>}
|
{a.description && <span>: {a.description}</span>}
|
||||||
</div>)}
|
</div>)}
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
{test && <TabbedPane tabs={
|
{test && <TabbedPane tabs={
|
||||||
test.results.map((result, index) => ({
|
test.results.map((result, index) => ({
|
||||||
id: String(index),
|
id: String(index),
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export const TestFileView: React.FC<{
|
||||||
<div key={`test-${test.testId}`} className={'test-file-test test-file-test-outcome-' + test.outcome}>
|
<div key={`test-${test.testId}`} className={'test-file-test test-file-test-outcome-' + test.outcome}>
|
||||||
<span style={{ float: 'right' }}>{msToString(test.duration)}</span>
|
<span style={{ float: 'right' }}>{msToString(test.duration)}</span>
|
||||||
{report.projectNames.length > 1 && !!test.projectName &&
|
{report.projectNames.length > 1 && !!test.projectName &&
|
||||||
<span style={{ float: 'right' }}><ProjectLink report={report} projectName={test.projectName}></ProjectLink></span>}
|
<span style={{ float: 'right' }}><ProjectLink projectNames={report.projectNames} projectName={test.projectName}></ProjectLink></span>}
|
||||||
{statusIcon(test.outcome)}
|
{statusIcon(test.outcome)}
|
||||||
<Link href={`#?testId=${test.testId}`} title={[...test.path, test.title].join(' › ')}>
|
<Link href={`#?testId=${test.testId}`} title={[...test.path, test.title].join(' › ')}>
|
||||||
{[...test.path, test.title].join(' › ')}
|
{[...test.path, test.title].join(' › ')}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import * as React from 'react';
|
||||||
import { TreeItem } from './treeItem';
|
import { TreeItem } from './treeItem';
|
||||||
import { TabbedPane } from './tabbedPane';
|
import { TabbedPane } from './tabbedPane';
|
||||||
import { msToString } from './uiUtils';
|
import { msToString } from './uiUtils';
|
||||||
import { Chip } from './chip';
|
import { AutoChip } from './chip';
|
||||||
import { traceImage } from './images';
|
import { traceImage } from './images';
|
||||||
import { AttachmentLink } from './links';
|
import { AttachmentLink } from './links';
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
|
|
@ -51,50 +51,50 @@ export const TestResultView: React.FC<{
|
||||||
const actual = attachmentsMap.get('actual');
|
const actual = attachmentsMap.get('actual');
|
||||||
const diff = attachmentsMap.get('diff');
|
const diff = attachmentsMap.get('diff');
|
||||||
return <div className='test-result'>
|
return <div className='test-result'>
|
||||||
{result.error && <Chip header='Errors'>
|
{result.error && <AutoChip header='Errors'>
|
||||||
<ErrorMessage key='test-result-error-message' error={result.error}></ErrorMessage>
|
<ErrorMessage key='test-result-error-message' error={result.error}></ErrorMessage>
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
{!!result.steps.length && <Chip header='Test Steps'>
|
{!!result.steps.length && <AutoChip header='Test Steps'>
|
||||||
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} depth={0}></StepTreeItem>)}
|
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} depth={0}></StepTreeItem>)}
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
|
|
||||||
{expected && actual && <Chip header='Image mismatch'>
|
{expected && actual && <AutoChip header='Image mismatch'>
|
||||||
<ImageDiff actual={actual} expected={expected} diff={diff}></ImageDiff>
|
<ImageDiff actual={actual} expected={expected} diff={diff}></ImageDiff>
|
||||||
<AttachmentLink key={`expected`} attachment={expected}></AttachmentLink>
|
<AttachmentLink key={`expected`} attachment={expected}></AttachmentLink>
|
||||||
<AttachmentLink key={`actual`} attachment={actual}></AttachmentLink>
|
<AttachmentLink key={`actual`} attachment={actual}></AttachmentLink>
|
||||||
{diff && <AttachmentLink key={`diff`} attachment={diff}></AttachmentLink>}
|
{diff && <AttachmentLink key={`diff`} attachment={diff}></AttachmentLink>}
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
|
|
||||||
{!!screenshots.length && <Chip header='Screenshots'>
|
{!!screenshots.length && <AutoChip header='Screenshots'>
|
||||||
{screenshots.map((a, i) => {
|
{screenshots.map((a, i) => {
|
||||||
return <div key={`screenshot-${i}`}>
|
return <div key={`screenshot-${i}`}>
|
||||||
<img src={a.path} />
|
<img src={a.path} />
|
||||||
<AttachmentLink attachment={a}></AttachmentLink>
|
<AttachmentLink attachment={a}></AttachmentLink>
|
||||||
</div>;
|
</div>;
|
||||||
})}
|
})}
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
|
|
||||||
{!!traces.length && <Chip header='Traces'>
|
{!!traces.length && <AutoChip header='Traces'>
|
||||||
{traces.map((a, i) => <div key={`trace-${i}`}>
|
{traces.map((a, i) => <div key={`trace-${i}`}>
|
||||||
<a href={`trace/index.html?trace=${new URL(a.path!, window.location.href)}`}>
|
<a href={`trace/index.html?trace=${new URL(a.path!, window.location.href)}`}>
|
||||||
<img src={traceImage} style={{ width: 192, height: 117, marginLeft: 20 }} />
|
<img src={traceImage} style={{ width: 192, height: 117, marginLeft: 20 }} />
|
||||||
</a>
|
</a>
|
||||||
<AttachmentLink attachment={a}></AttachmentLink>
|
<AttachmentLink attachment={a}></AttachmentLink>
|
||||||
</div>)}
|
</div>)}
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
|
|
||||||
{!!videos.length && <Chip header='Videos'>
|
{!!videos.length && <AutoChip header='Videos'>
|
||||||
{videos.map((a, i) => <div key={`video-${i}`}>
|
{videos.map((a, i) => <div key={`video-${i}`}>
|
||||||
<video controls>
|
<video controls>
|
||||||
<source src={a.path} type={a.contentType}/>
|
<source src={a.path} type={a.contentType}/>
|
||||||
</video>
|
</video>
|
||||||
<AttachmentLink attachment={a}></AttachmentLink>
|
<AttachmentLink attachment={a}></AttachmentLink>
|
||||||
</div>)}
|
</div>)}
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
|
|
||||||
{!!otherAttachments.length && <Chip header='Attachments'>
|
{!!otherAttachments.length && <AutoChip header='Attachments'>
|
||||||
{otherAttachments.map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)}
|
{otherAttachments.map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)}
|
||||||
</Chip>}
|
</AutoChip>}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,21 +57,15 @@ const Component = ({ style, children }) => {
|
||||||
|
|
||||||
const registry = new Map();
|
const registry = new Map();
|
||||||
|
|
||||||
export const registerComponent = (name, component, options) => {
|
export const registerComponent = (name, component) => {
|
||||||
registry.set(name, { component, options });
|
registry.set(name, component);
|
||||||
};
|
};
|
||||||
|
|
||||||
function render(name, params) {
|
function render(name, params) {
|
||||||
const entry = registry.get(name);
|
const component = registry.get(name);
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
React.createElement(Component, null, React.createElement(entry.component, params || null)),
|
React.createElement(Component, null, React.createElement(component, params || null)),
|
||||||
document.getElementById('root'));
|
document.getElementById('root'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function options(name) {
|
|
||||||
const entry = registry.get(name);
|
|
||||||
return entry.options;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.__playwright_render = render;
|
window.__playwright_render = render;
|
||||||
window.__playwright_options = options;
|
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,21 @@ import { test as baseTest, Locator } from '@playwright/test';
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__playwright_render: (component: string, props: any) => void;
|
__playwright_render: (component: string, props: any) => void;
|
||||||
__playwright_options: (component: string) => { viewport: { width: number, height: number } };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestFixtures = {
|
type TestFixtures = {
|
||||||
renderComponent: (component: string, params?: any) => Promise<Locator>;
|
render: (component: { type: string, props: Object }) => Promise<Locator>;
|
||||||
webpack: string;
|
webpack: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const test = baseTest.extend<TestFixtures>({
|
export const test = baseTest.extend<TestFixtures>({
|
||||||
webpack: '',
|
webpack: '',
|
||||||
renderComponent: async ({ page, webpack }, use, testInfo) => {
|
render: async ({ page, webpack }, use) => {
|
||||||
const webpackConfig = require(webpack);
|
const webpackConfig = require(webpack);
|
||||||
const outputPath = webpackConfig.output.path;
|
const outputPath = webpackConfig.output.path;
|
||||||
const filename = webpackConfig.output.filename.replace('[name]', 'playwright');
|
const filename = webpackConfig.output.filename.replace('[name]', 'playwright');
|
||||||
await use(async (component: string, optionalParams?: Object) => {
|
await use(async (component: { type: string, props: Object }) => {
|
||||||
await page.route('http://component/index.html', route => {
|
await page.route('http://component/index.html', route => {
|
||||||
route.fulfill({
|
route.fulfill({
|
||||||
body: `<html>
|
body: `<html>
|
||||||
|
|
@ -49,27 +48,23 @@ export const test = baseTest.extend<TestFixtures>({
|
||||||
await page.goto('http://component/index.html');
|
await page.goto('http://component/index.html');
|
||||||
|
|
||||||
await page.addScriptTag({ path: path.resolve(__dirname, outputPath, filename) });
|
await page.addScriptTag({ path: path.resolve(__dirname, outputPath, filename) });
|
||||||
const options = await page.evaluate((component: string) => {
|
|
||||||
return window.__playwright_options(component);
|
|
||||||
}, component);
|
|
||||||
await page.setViewportSize(options.viewport);
|
|
||||||
|
|
||||||
const params = { ...optionalParams };
|
const props = { ...component.props };
|
||||||
for (const [key, value] of Object.entries(params)) {
|
for (const [key, value] of Object.entries(props)) {
|
||||||
if (typeof value === 'function') {
|
if (typeof value === 'function') {
|
||||||
const functionName = '__pw_func_' + key;
|
const functionName = '__pw_func_' + key;
|
||||||
await page.exposeFunction(functionName, value);
|
await page.exposeFunction(functionName, value);
|
||||||
(params as any)[key] = functionName;
|
(props as any)[key] = functionName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await page.evaluate(v => {
|
await page.evaluate(v => {
|
||||||
const params = v.params;
|
const props = v.props;
|
||||||
for (const [key, value] of Object.entries(params)) {
|
for (const [key, value] of Object.entries(props)) {
|
||||||
if (typeof value === 'string' && (value as string).startsWith('__pw_func_'))
|
if (typeof value === 'string' && (value as string).startsWith('__pw_func_'))
|
||||||
(params as any)[key] = window[value];
|
(props as any)[key] = (window as any)[value];
|
||||||
}
|
}
|
||||||
window.__playwright_render(v.component, params);
|
window.__playwright_render(v.type, props);
|
||||||
}, { component, params });
|
}, { type: component.type, props });
|
||||||
return page.locator('#pw-root');
|
return page.locator('#pw-root');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
zip: require.resolve('@zip.js/zip.js/dist/zip-no-worker-inflate.min.js'),
|
zip: require.resolve('@zip.js/zip.js/dist/zip-no-worker-inflate.min.js'),
|
||||||
app: path.join(__dirname, 'src', 'index.tsx'),
|
app: path.join(__dirname, 'src', 'index.tsx'),
|
||||||
playwright: path.join(__dirname, 'playwright.stories.tsx'),
|
playwright: path.join(__dirname, 'playwright.components.tsx'),
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.js', '.tsx', '.jsx']
|
extensions: ['.ts', '.js', '.tsx', '.jsx']
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||||
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
|
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
|
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
|
||||||
|
"@babel/plugin-transform-react-jsx": "^7.14.5",
|
||||||
"@babel/preset-typescript": "^7.14.5",
|
"@babel/preset-typescript": "^7.14.5",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,15 @@ async function resolve(specifier: string, context: { parentURL: string }, defaul
|
||||||
return defaultResolve(specifier, context, defaultResolve);
|
return defaultResolve(specifier, context, defaultResolve);
|
||||||
let url = new URL(specifier, context.parentURL).toString();
|
let url = new URL(specifier, context.parentURL).toString();
|
||||||
url = url.substring('file://'.length);
|
url = url.substring('file://'.length);
|
||||||
if (fs.existsSync(url + '.ts'))
|
for (const extension of ['.ts', '.js', '.tsx', '.jsx']) {
|
||||||
return defaultResolve(specifier + '.ts', context, defaultResolve);
|
if (fs.existsSync(url + extension))
|
||||||
if (fs.existsSync(url + '.js'))
|
return defaultResolve(specifier + extension, context, defaultResolve);
|
||||||
return defaultResolve(specifier + '.js', context, defaultResolve);
|
}
|
||||||
return defaultResolve(specifier, context, defaultResolve);
|
return defaultResolve(specifier, context, defaultResolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(url: string, context: any, defaultLoad: any) {
|
async function load(url: string, context: any, defaultLoad: any) {
|
||||||
if (url.endsWith('.ts')) {
|
if (url.endsWith('.ts') || url.endsWith('.tsx')) {
|
||||||
const filename = url.substring('file://'.length);
|
const filename = url.substring('file://'.length);
|
||||||
const cwd = path.dirname(filename);
|
const cwd = path.dirname(filename);
|
||||||
let tsconfig = tsConfigCache.get(cwd);
|
let tsconfig = tsConfigCache.get(cwd);
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ export class Loader {
|
||||||
testDir,
|
testDir,
|
||||||
snapshotDir,
|
snapshotDir,
|
||||||
testIgnore: takeFirst(this._configOverrides.testIgnore, projectConfig.testIgnore, this._config.testIgnore, []),
|
testIgnore: takeFirst(this._configOverrides.testIgnore, projectConfig.testIgnore, this._config.testIgnore, []),
|
||||||
testMatch: takeFirst(this._configOverrides.testMatch, projectConfig.testMatch, this._config.testMatch, '**/?(*.)@(spec|test).@(ts|js|mjs)'),
|
testMatch: takeFirst(this._configOverrides.testMatch, projectConfig.testMatch, this._config.testMatch, '**/?(*.)@(spec|test).*'),
|
||||||
timeout: takeFirst(this._configOverrides.timeout, projectConfig.timeout, this._config.timeout, 10000),
|
timeout: takeFirst(this._configOverrides.timeout, projectConfig.timeout, this._config.timeout, 10000),
|
||||||
use: mergeObjects(mergeObjects(this._config.use, projectConfig.use), this._configOverrides.use),
|
use: mergeObjects(mergeObjects(this._config.use, projectConfig.use), this._configOverrides.use),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,8 @@ export class Runner {
|
||||||
const allFiles = await collectFiles(project.config.testDir);
|
const allFiles = await collectFiles(project.config.testDir);
|
||||||
const testMatch = createFileMatcher(project.config.testMatch);
|
const testMatch = createFileMatcher(project.config.testMatch);
|
||||||
const testIgnore = createFileMatcher(project.config.testIgnore);
|
const testIgnore = createFileMatcher(project.config.testIgnore);
|
||||||
const testFileExtension = (file: string) => ['.js', '.ts', '.mjs'].includes(path.extname(file));
|
const extensions = ['.js', '.ts', '.mjs', ...(process.env.PW_COMPONENT_TESTING ? ['.tsx', '.jsx'] : [])];
|
||||||
|
const testFileExtension = (file: string) => extensions.includes(path.extname(file));
|
||||||
const testFiles = allFiles.filter(file => !testIgnore(file) && testMatch(file) && testFileFilter(file) && testFileExtension(file));
|
const testFiles = allFiles.filter(file => !testIgnore(file) && testMatch(file) && testFileFilter(file) && testFileExtension(file));
|
||||||
files.set(project, testFiles);
|
files.set(project, testFiles);
|
||||||
testFiles.forEach(file => allTestFiles.add(file));
|
testFiles.forEach(file => allTestFiles.add(file));
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import * as url from 'url';
|
||||||
import type { Location } from './types';
|
import type { Location } from './types';
|
||||||
import { TsConfigLoaderResult } from './third_party/tsconfig-loader';
|
import { TsConfigLoaderResult } from './third_party/tsconfig-loader';
|
||||||
|
|
||||||
const version = 4;
|
const version = 5;
|
||||||
const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwright-transform-cache');
|
const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwright-transform-cache');
|
||||||
const sourceMaps: Map<string, string> = new Map();
|
const sourceMaps: Map<string, string> = new Map();
|
||||||
|
|
||||||
|
|
@ -54,6 +54,8 @@ function calculateCachePath(content: string, filePath: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformHook(code: string, filename: string, tsconfig: TsConfigLoaderResult, isModule = false): string {
|
export function transformHook(code: string, filename: string, tsconfig: TsConfigLoaderResult, isModule = false): string {
|
||||||
|
if (isComponentImport(filename))
|
||||||
|
return componentStub();
|
||||||
const cachePath = calculateCachePath(code, filename);
|
const cachePath = calculateCachePath(code, filename);
|
||||||
const codePath = cachePath + '.js';
|
const codePath = cachePath + '.js';
|
||||||
const sourceMapPath = cachePath + '.map';
|
const sourceMapPath = cachePath + '.map';
|
||||||
|
|
@ -65,7 +67,7 @@ export function transformHook(code: string, filename: string, tsconfig: TsConfig
|
||||||
process.env.BROWSERSLIST_IGNORE_OLD_DATA = 'true';
|
process.env.BROWSERSLIST_IGNORE_OLD_DATA = 'true';
|
||||||
const babel: typeof import('@babel/core') = require('@babel/core');
|
const babel: typeof import('@babel/core') = require('@babel/core');
|
||||||
|
|
||||||
const alias: { [key: string]: string | ((s: string[]) => string) } = {};
|
const extensions = ['', '.js', '.ts', '.mjs', ...(process.env.PW_COMPONENT_TESTING ? ['.tsx', '.jsx'] : [])]; const alias: { [key: string]: string | ((s: string[]) => string) } = {};
|
||||||
for (const [key, values] of Object.entries(tsconfig.paths || {})) {
|
for (const [key, values] of Object.entries(tsconfig.paths || {})) {
|
||||||
const regexKey = '^' + key.replace('*', '.*');
|
const regexKey = '^' + key.replace('*', '.*');
|
||||||
alias[regexKey] = ([name]) => {
|
alias[regexKey] = ([name]) => {
|
||||||
|
|
@ -73,8 +75,10 @@ export function transformHook(code: string, filename: string, tsconfig: TsConfig
|
||||||
const relative = (key.endsWith('/*') ? value.substring(0, value.length - 1) + name.substring(key.length - 1) : value)
|
const relative = (key.endsWith('/*') ? value.substring(0, value.length - 1) + name.substring(key.length - 1) : value)
|
||||||
.replace(/\//g, path.sep);
|
.replace(/\//g, path.sep);
|
||||||
const result = path.resolve(tsconfig.baseUrl || '', relative);
|
const result = path.resolve(tsconfig.baseUrl || '', relative);
|
||||||
if (fs.existsSync(result) || fs.existsSync(result + '.js') || fs.existsSync(result + '.ts'))
|
for (const extension of extensions) {
|
||||||
return result;
|
if (fs.existsSync(result + extension))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
@ -96,6 +100,10 @@ export function transformHook(code: string, filename: string, tsconfig: TsConfig
|
||||||
alias
|
alias
|
||||||
}],
|
}],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (process.env.PW_COMPONENT_TESTING)
|
||||||
|
plugins.unshift([require.resolve('@babel/plugin-transform-react-jsx')]);
|
||||||
|
|
||||||
if (!isModule) {
|
if (!isModule) {
|
||||||
plugins.push([require.resolve('@babel/plugin-transform-modules-commonjs')]);
|
plugins.push([require.resolve('@babel/plugin-transform-modules-commonjs')]);
|
||||||
plugins.push([require.resolve('@babel/plugin-proposal-dynamic-import')]);
|
plugins.push([require.resolve('@babel/plugin-proposal-dynamic-import')]);
|
||||||
|
|
@ -125,7 +133,7 @@ export function transformHook(code: string, filename: string, tsconfig: TsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export function installTransform(tsconfig: TsConfigLoaderResult): () => void {
|
export function installTransform(tsconfig: TsConfigLoaderResult): () => void {
|
||||||
return pirates.addHook((code: string, filename: string) => transformHook(code, filename, tsconfig), { exts: ['.ts'] });
|
return pirates.addHook((code: string, filename: string) => transformHook(code, filename, tsconfig), { exts: ['.ts', '.tsx'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapFunctionWithLocation<A extends any[], R>(func: (location: Location, ...args: A) => R): (...args: A) => R {
|
export function wrapFunctionWithLocation<A extends any[], R>(func: (location: Location, ...args: A) => R): (...args: A) => R {
|
||||||
|
|
@ -151,3 +159,20 @@ export function wrapFunctionWithLocation<A extends any[], R>(func: (location: Lo
|
||||||
return func(location, ...args);
|
return func(location, ...args);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Experimental components support for internal testing.
|
||||||
|
function isComponentImport(filename: string): boolean {
|
||||||
|
if (!process.env.PW_COMPONENT_TESTING)
|
||||||
|
return false;
|
||||||
|
if (filename.endsWith('.tsx') && !filename.endsWith('spec.tsx') && !filename.endsWith('test.tsx'))
|
||||||
|
return true;
|
||||||
|
if (filename.endsWith('.jsx') && !filename.endsWith('spec.jsx') && !filename.endsWith('test.jsx'))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function componentStub(): string {
|
||||||
|
return `module.exports = new Proxy({}, {
|
||||||
|
get: (obj, prop) => prop
|
||||||
|
});`;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue