chore: add experimental mount (#12657)
This commit is contained in:
Родитель
d7c1a57565
Коммит
af55f48754
|
@ -1117,6 +1117,18 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/ct-react": {
|
||||
"resolved": "packages/playwright-ct-react",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@playwright/ct-svelte": {
|
||||
"resolved": "packages/playwright-ct-svelte",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@playwright/ct-vue": {
|
||||
"resolved": "packages/playwright-ct-vue",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"resolved": "packages/playwright-test",
|
||||
"link": true
|
||||
|
@ -7364,6 +7376,15 @@
|
|||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"packages/playwright-ct-react": {
|
||||
"name": "@playwright/ct-react"
|
||||
},
|
||||
"packages/playwright-ct-svelte": {
|
||||
"name": "@playwright/ct-svelte"
|
||||
},
|
||||
"packages/playwright-ct-vue": {
|
||||
"name": "@playwright/ct-vue"
|
||||
},
|
||||
"packages/playwright-firefox": {
|
||||
"version": "1.21.0-next",
|
||||
"hasInstallScript": true,
|
||||
|
@ -8195,6 +8216,15 @@
|
|||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@playwright/ct-react": {
|
||||
"version": "file:packages/playwright-ct-react"
|
||||
},
|
||||
"@playwright/ct-svelte": {
|
||||
"version": "file:packages/playwright-ct-svelte"
|
||||
},
|
||||
"@playwright/ct-vue": {
|
||||
"version": "file:packages/playwright-ct-vue"
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "file:packages/playwright-test",
|
||||
"requires": {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
out-ct/
|
|
@ -0,0 +1,28 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='color-scheme' content='dark light'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>Playwright CT</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id=root></div>
|
||||
</body>
|
||||
</html>
|
|
@ -14,11 +14,12 @@
|
|||
* 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 { registerComponent } from './test/component';
|
||||
import { AutoChip, Chip } from '../src/chip';
|
||||
import { HeaderView } from '../src/headerView';
|
||||
import { TestCaseView } from '../src/testCaseView';
|
||||
import '../src/theme.css';
|
||||
|
||||
import { registerComponent } from '@playwright/ct-react/render';
|
||||
|
||||
registerComponent('HeaderView', HeaderView);
|
||||
registerComponent('Chip', Chip);
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const HtmlWebPackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
|
||||
|
||||
module.exports = {
|
||||
mode,
|
||||
entry: {
|
||||
index: path.join(__dirname, 'index.ts'),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.tsx', '.jsx']
|
||||
},
|
||||
devtool: mode === 'production' ? false : 'source-map',
|
||||
output: {
|
||||
globalObject: 'self',
|
||||
filename: '[name].bundle.js',
|
||||
path: path.resolve(__dirname, '..', 'out-ct')
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(j|t)sx?$/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
"@babel/preset-typescript",
|
||||
"@babel/preset-react"
|
||||
]
|
||||
},
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebPackPlugin({
|
||||
title: 'Playwright CT',
|
||||
template: path.join(__dirname, 'index.html'),
|
||||
inject: true,
|
||||
}),
|
||||
]
|
||||
};
|
|
@ -15,10 +15,11 @@
|
|||
*/
|
||||
|
||||
import { PlaywrightTestConfig, devices } from '@playwright/test';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: 'src',
|
||||
snapshotDir: 'snapshots',
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: process.env.CI ? [
|
||||
|
@ -27,6 +28,7 @@ const config: PlaywrightTestConfig = {
|
|||
['html', { open: 'on-failure' }]
|
||||
],
|
||||
use: {
|
||||
baseURL: url.pathToFileURL(path.join(__dirname, 'out-ct', 'index.html')).toString(),
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
|
|
|
@ -15,10 +15,9 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { expect, test } from '../test/componentTest';
|
||||
import { expect, test } from '@playwright/ct-react/test';
|
||||
import { AutoChip, Chip } from './chip';
|
||||
|
||||
test.use({ webpack: require.resolve('../webpack.config.js') });
|
||||
test.use({ viewport: { width: 500, height: 500 } });
|
||||
|
||||
test('expand collapse', async ({ mount }) => {
|
||||
|
|
|
@ -15,10 +15,9 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { test, expect } from '../test/componentTest';
|
||||
import { test, expect } from '@playwright/ct-react/test';
|
||||
import { HeaderView } from './headerView';
|
||||
|
||||
test.use({ webpack: require.resolve('../webpack.config.js') });
|
||||
test.use({ viewport: { width: 720, height: 200 } });
|
||||
|
||||
test('should render counters', async ({ mount }) => {
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { test, expect } from '../test/componentTest';
|
||||
import { test, expect } from '@playwright/ct-react/test';
|
||||
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 = {
|
||||
|
|
|
@ -1,77 +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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
const fillStyle = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
};
|
||||
|
||||
const checkerboardCommon = {
|
||||
...fillStyle,
|
||||
backgroundSize: '50px 50px',
|
||||
backgroundPosition: '0 0, 25px 25px',
|
||||
};
|
||||
|
||||
const checkerboardLight = {
|
||||
...checkerboardCommon,
|
||||
backgroundColor: '#FFF',
|
||||
backgroundImage: `linear-gradient(45deg, #00000008 25%, transparent 25%, transparent 75%, #00000008 75%, #00000008),
|
||||
linear-gradient(45deg, #00000008 25%, transparent 25%, transparent 75%, #00000008 75%, #00000008)`
|
||||
};
|
||||
|
||||
const checkerboardDark = {
|
||||
...checkerboardCommon,
|
||||
backgroundColor: '#000',
|
||||
backgroundImage: `linear-gradient(45deg, #FFFFFF12 25%, transparent 25%, transparent 75%, #FFFFFF12 75%, #FFFFFF12),
|
||||
linear-gradient(45deg, #FFFFFF12 25%, transparent 25%, transparent 75%, #FFFFFF12 75%, #FFFFFF12)`
|
||||
};
|
||||
|
||||
const Component = ({ style, children }) => {
|
||||
const checkerboard = window.matchMedia('(prefers-color-scheme: dark)').matches ? checkerboardDark : checkerboardLight;
|
||||
const bgStyle = { ...checkerboard };
|
||||
const fgStyle = { ...fillStyle, ...style };
|
||||
return React.createElement(
|
||||
React.Fragment, null,
|
||||
React.createElement('div', { style: bgStyle }),
|
||||
React.createElement('div', { style: fgStyle, id: 'pw-root' }, children));
|
||||
};
|
||||
|
||||
const registry = new Map();
|
||||
|
||||
export const registerComponent = (name, componentFunc) => {
|
||||
registry.set(name, componentFunc);
|
||||
};
|
||||
|
||||
function render(component) {
|
||||
const componentFunc = registry.get(component.type) || component.type;
|
||||
return React.createElement(componentFunc, component.props, ...component.children.map(child => {
|
||||
if (typeof child === 'string')
|
||||
return child;
|
||||
return render(child);
|
||||
}));
|
||||
}
|
||||
|
||||
window.__playwright_render = component => {
|
||||
ReactDOM.render(
|
||||
React.createElement(Component, null, render(component)),
|
||||
document.getElementById('root'));
|
||||
};
|
|
@ -1,79 +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 * as path from 'path';
|
||||
import { test as baseTest, Locator } from '@playwright/test';
|
||||
|
||||
type Component = {
|
||||
type: string;
|
||||
props: Object;
|
||||
children: Object[];
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__playwright_render: (component: Component) => void;
|
||||
}
|
||||
}
|
||||
|
||||
type TestFixtures = {
|
||||
mount: (component: any) => Promise<Locator>;
|
||||
webpack: string;
|
||||
};
|
||||
|
||||
export const test = baseTest.extend<TestFixtures>({
|
||||
webpack: '',
|
||||
mount: async ({ page, webpack }, use) => {
|
||||
const webpackConfig = require(webpack);
|
||||
const outputPath = webpackConfig.output.path;
|
||||
const filename = webpackConfig.output.filename.replace('[name]', 'playwright');
|
||||
await use(async (component: Component) => {
|
||||
await page.route('http://component/index.html', route => {
|
||||
route.fulfill({
|
||||
body: `<html>
|
||||
<meta name='color-scheme' content='dark light'>
|
||||
<style>html, body { padding: 0; margin: 0; background: #aaa; }</style>
|
||||
<div id='root' style='width: 100%; height: 100%;'></div>
|
||||
</html>`,
|
||||
contentType: 'text/html'
|
||||
});
|
||||
});
|
||||
await page.goto('http://component/index.html');
|
||||
|
||||
await page.addScriptTag({ path: path.resolve(__dirname, outputPath, filename) });
|
||||
|
||||
const props = { ...component.props };
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (typeof value === 'function') {
|
||||
const functionName = '__pw_func_' + key;
|
||||
await page.exposeFunction(functionName, value);
|
||||
(props as any)[key] = functionName;
|
||||
}
|
||||
}
|
||||
await page.evaluate(v => {
|
||||
const props = v.props;
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (typeof value === 'string' && (value as string).startsWith('__pw_func_'))
|
||||
(props as any)[key] = (window as any)[value];
|
||||
}
|
||||
window.__playwright_render({ ...v, props });
|
||||
}, { ...component, props });
|
||||
return page.locator('#pw-root');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export { expect } from '@playwright/test';
|
|
@ -25,7 +25,6 @@ module.exports = {
|
|||
entry: {
|
||||
zip: require.resolve('@zip.js/zip.js/dist/zip-no-worker-inflate.min.js'),
|
||||
app: path.join(__dirname, 'src', 'index.tsx'),
|
||||
playwright: path.join(__dirname, 'playwright.components.tsx'),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.tsx', '.jsx']
|
||||
|
@ -60,7 +59,6 @@ module.exports = {
|
|||
title: 'Playwright Test Report',
|
||||
template: path.join(__dirname, 'src', 'index.html'),
|
||||
inject: true,
|
||||
excludeChunks: ['playwright'],
|
||||
}),
|
||||
new BundleJsPlugin(),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@playwright/ct-react",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./render": "./render.mjs",
|
||||
"./test": "./test.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const registerComponent: (name: string, componentFunc: any) => void;
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 ReactDOM from 'react-dom';
|
||||
|
||||
const registry = new Map();
|
||||
|
||||
export const registerComponent = (name, componentFunc) => {
|
||||
registry.set(name, componentFunc);
|
||||
};
|
||||
|
||||
function render(component) {
|
||||
const componentFunc = registry.get(component.type) || component.type;
|
||||
return React.createElement(componentFunc, component.props, ...component.children.map(child => {
|
||||
if (typeof child === 'string')
|
||||
return child;
|
||||
return render(child);
|
||||
}));
|
||||
}
|
||||
|
||||
window.playwrightMount = component => {
|
||||
ReactDOM.render(render(component), document.getElementById('root'));
|
||||
return '#root';
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* 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 type {
|
||||
TestType,
|
||||
PlaywrightTestArgs,
|
||||
PlaywrightTestOptions,
|
||||
PlaywrightWorkerArgs,
|
||||
PlaywrightWorkerOptions,
|
||||
Locator,
|
||||
} from '@playwright/test';
|
||||
|
||||
interface ComponentFixtures {
|
||||
mount(component: JSX.Element): Promise<Locator>;
|
||||
}
|
||||
|
||||
export const test: TestType<
|
||||
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
|
||||
PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
||||
|
||||
export { expect } from '@playwright/test';
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { test: baseTest, expect } = require('@playwright/test');
|
||||
const { mount } = require('@playwright/test/lib/mount');
|
||||
|
||||
const test = baseTest.extend({
|
||||
mount: async ({ page, baseURL }, use) => {
|
||||
await use(async (component, options) => {
|
||||
await page.goto(baseURL);
|
||||
const selector = await mount(page, component, options);
|
||||
return page.locator(selector);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = { test, expect };
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@playwright/ct-svelte",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./render": "./render.mjs",
|
||||
"./test": "./test.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const registerComponent: (name: string, componentFunc: any) => void;
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const registry = new Map();
|
||||
|
||||
export const registerComponent = (name, componentClass) => {
|
||||
registry.set(name, componentClass);
|
||||
};
|
||||
|
||||
window.playwrightMount = component => {
|
||||
const componentCtor = registry.get(component.type);
|
||||
|
||||
const wrapper = new componentCtor({
|
||||
target: document.getElementById('app'),
|
||||
props: component.options.props,
|
||||
});
|
||||
|
||||
for (const [key, listener] of Object.entries(component.options.on || {}))
|
||||
wrapper.$on(key, event => listener(event.detail));
|
||||
return '#app';
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 type {
|
||||
TestType,
|
||||
PlaywrightTestArgs,
|
||||
PlaywrightTestOptions,
|
||||
PlaywrightWorkerArgs,
|
||||
PlaywrightWorkerOptions,
|
||||
Locator,
|
||||
} from '@playwright/test';
|
||||
|
||||
interface ComponentFixtures {
|
||||
mount(component: any, options?: {
|
||||
props?: { [key: string]: any },
|
||||
slots?: { [key: string]: any },
|
||||
on?: { [key: string]: Function },
|
||||
}): Promise<Locator>;
|
||||
}
|
||||
|
||||
export const test: TestType<
|
||||
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
|
||||
PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
||||
|
||||
export { expect } from '@playwright/test';
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { test: baseTest, expect } = require('@playwright/test');
|
||||
const { mount } = require('@playwright/test/lib/mount');
|
||||
|
||||
const test = baseTest.extend({
|
||||
mount: async ({ page, baseURL }, use) => {
|
||||
await use(async (component, options) => {
|
||||
await page.goto(baseURL);
|
||||
const selector = await mount(page, component, options);
|
||||
return page.locator(selector);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = { test, expect };
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@playwright/ct-vue",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./render": "./render.mjs",
|
||||
"./test": "./test.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const initVueTest: (vue: any) => void;
|
||||
export const registerComponent: (name: string, componentFunc: any) => void;
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* 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 { createApp, setDevtoolsHook, h } from 'vue';
|
||||
|
||||
const registry = new Map();
|
||||
let instance = { createApp, setDevtoolsHook, h };
|
||||
|
||||
export const initVueTest = vue => {
|
||||
instance = vue;
|
||||
};
|
||||
|
||||
export const registerComponent = (name, vueComponent) => {
|
||||
registry.set(name, vueComponent);
|
||||
};
|
||||
|
||||
const allListeners = [];
|
||||
|
||||
function render(component) {
|
||||
if (typeof component === 'string')
|
||||
return component;
|
||||
const componentFunc = registry.get(component.type) || component.type;
|
||||
|
||||
const children = [];
|
||||
const slots = {};
|
||||
const listeners = {};
|
||||
let props = {};
|
||||
|
||||
if (component.kind === 'jsx') {
|
||||
for (const child of component.children || []) {
|
||||
if (child.type === 'template') {
|
||||
const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:'));
|
||||
const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default';
|
||||
slots[slot] = child.children.map(render);
|
||||
} else {
|
||||
children.push(render(child));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(component.props)) {
|
||||
if (key.startsWith('v-on:'))
|
||||
listeners[key.substring('v-on:'.length)] = value;
|
||||
else
|
||||
props[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (component.kind === 'object') {
|
||||
// Vue test util syntax.
|
||||
for (const [key, value] of Object.entries(component.options.slots || {})) {
|
||||
if (key === 'default')
|
||||
children.push(value);
|
||||
else
|
||||
slots[key] = value;
|
||||
}
|
||||
props = component.options.props || {};
|
||||
for (const [key, value] of Object.entries(component.options.on || {}))
|
||||
listeners[key] = value;
|
||||
}
|
||||
|
||||
let lastArg;
|
||||
if (Object.entries(slots).length) {
|
||||
lastArg = slots;
|
||||
if (children.length)
|
||||
slots.default = children;
|
||||
} else if (children.length) {
|
||||
lastArg = children;
|
||||
}
|
||||
|
||||
const wrapper = instance.h(componentFunc, props, lastArg);
|
||||
allListeners.push([wrapper, listeners]);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function createDevTools() {
|
||||
return {
|
||||
emit(eventType, ...payload) {
|
||||
if (eventType === 'component:emit') {
|
||||
const [, componentVM, event, eventArgs] = payload;
|
||||
for (const [wrapper, listeners] of allListeners) {
|
||||
if (wrapper.component !== componentVM)
|
||||
continue;
|
||||
const listener = listeners[event];
|
||||
if (!listener)
|
||||
return;
|
||||
listener(...eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
window.playwrightMount = async component => {
|
||||
const app = instance.createApp({
|
||||
render: () => render(component)
|
||||
});
|
||||
instance.setDevtoolsHook(createDevTools(), {});
|
||||
app.mount('#app');
|
||||
return '#app';
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 type {
|
||||
TestType,
|
||||
PlaywrightTestArgs,
|
||||
PlaywrightTestOptions,
|
||||
PlaywrightWorkerArgs,
|
||||
PlaywrightWorkerOptions,
|
||||
Locator,
|
||||
} from '@playwright/test';
|
||||
|
||||
interface ComponentFixtures {
|
||||
mount(component: JSX.Element): Promise<Locator>;
|
||||
mount(component: any, options?: {
|
||||
props?: { [key: string]: any },
|
||||
slots?: { [key: string]: any },
|
||||
on?: { [key: string]: Function },
|
||||
}): Promise<Locator>;
|
||||
}
|
||||
|
||||
export const test: TestType<
|
||||
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
|
||||
PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
||||
|
||||
export { expect } from '@playwright/test';
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { test: baseTest, expect } = require('@playwright/test');
|
||||
const { mount } = require('@playwright/test/lib/mount');
|
||||
|
||||
const test = baseTest.extend({
|
||||
mount: async ({ page, baseURL }, use) => {
|
||||
await use(async (component, options) => {
|
||||
await page.goto(baseURL);
|
||||
const selector = await mount(page, component, options);
|
||||
return page.locator(selector);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = { test, expect };
|
|
@ -17,4 +17,3 @@
|
|||
export * from 'playwright-core';
|
||||
export * from './types/test';
|
||||
export { default } from './types/test';
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
"./package.json": "./package.json",
|
||||
"./lib/cli": "./lib/cli.js",
|
||||
"./lib/experimentalLoader": "./lib/experimentalLoader.js",
|
||||
"./lib/mount": "./lib/mount.js",
|
||||
"./reporter": "./reporter.js"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* 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 { Page } from '@playwright/test';
|
||||
import { createGuid } from 'playwright-core/lib/utils/utils';
|
||||
|
||||
export async function mount(page: Page, jsxOrType: any, options: any): Promise<string> {
|
||||
let component;
|
||||
if (typeof jsxOrType === 'string')
|
||||
component = { kind: 'object', type: jsxOrType, options };
|
||||
else
|
||||
component = jsxOrType;
|
||||
|
||||
const callbacks: Function[] = [];
|
||||
wrapFunctions(component, page, callbacks);
|
||||
|
||||
|
||||
const dispatchMethod = `__pw_dispatch_${createGuid}`;
|
||||
|
||||
await page.exposeFunction(dispatchMethod, (ordinal: number, args: any[]) => {
|
||||
callbacks[ordinal](...args);
|
||||
});
|
||||
|
||||
const selector = await page.evaluate(async ({ component, dispatchMethod }) => {
|
||||
const unwrapFunctions = (object: any) => {
|
||||
for (const [key, value] of Object.entries(object)) {
|
||||
if (typeof value === 'string' && (value as string).startsWith('__pw_func_')) {
|
||||
const ordinal = +value.substring('__pw_func_'.length);
|
||||
object[key] = (...args: any[]) => {
|
||||
(window as any)[dispatchMethod](ordinal, args);
|
||||
};
|
||||
} else if (typeof value === 'object' && value) {
|
||||
unwrapFunctions(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unwrapFunctions(component);
|
||||
return await (window as any).playwrightMount(component);
|
||||
}, { component, dispatchMethod });
|
||||
return selector;
|
||||
}
|
||||
|
||||
function wrapFunctions(object: any, page: Page, callbacks: Function[]) {
|
||||
for (const [key, value] of Object.entries(object)) {
|
||||
const type = typeof value;
|
||||
if (type === 'function') {
|
||||
const functionName = '__pw_func_' + callbacks.length;
|
||||
callbacks.push(value as Function);
|
||||
object[key] = functionName;
|
||||
} else if (type === 'object' && value) {
|
||||
wrapFunctions(value, page, callbacks);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ import type { Location } from './types';
|
|||
import { tsConfigLoader, TsConfigLoaderResult } from './third_party/tsconfig-loader';
|
||||
import Module from 'module';
|
||||
|
||||
const version = 7;
|
||||
const version = 8;
|
||||
const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwright-transform-cache');
|
||||
const sourceMaps: Map<string, string> = new Map();
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { types as t } from '@babel/core';
|
||||
import { types as t, NodePath } from '@babel/core';
|
||||
import { declare } from '@babel/helper-plugin-utils';
|
||||
|
||||
export default declare(api => {
|
||||
|
@ -23,6 +23,44 @@ export default declare(api => {
|
|||
return {
|
||||
name: 'playwright-debug-transform',
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.setData('pw-components', new Map());
|
||||
},
|
||||
|
||||
ImportDeclaration(path) {
|
||||
// Non-JSX transform, replace
|
||||
// import Button from './ButtonVue.vue'
|
||||
// import { Card as MyCard } from './Card.vue'
|
||||
// with
|
||||
// const Button 'Button', MyCard = 'Card';
|
||||
const importNode = path.node;
|
||||
if (!t.isStringLiteral(importNode.source)) {
|
||||
flushConst(path, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!importNode.source.value.endsWith('.vue') && !importNode.source.value.endsWith('.svelte')) {
|
||||
flushConst(path, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const components = path.parentPath.getData('pw-components');
|
||||
for (const specifier of importNode.specifiers) {
|
||||
if (t.isImportDefaultSpecifier(specifier)) {
|
||||
components.set(specifier.local.name, specifier.local.name);
|
||||
continue;
|
||||
}
|
||||
if (t.isImportSpecifier(specifier)) {
|
||||
if (t.isIdentifier(specifier.imported))
|
||||
components.set(specifier.local.name, specifier.imported.name);
|
||||
else
|
||||
components.set(specifier.local.name, specifier.imported.value);
|
||||
}
|
||||
}
|
||||
|
||||
flushConst(path, false);
|
||||
},
|
||||
|
||||
JSXElement(path) {
|
||||
const jsxElement = path.node;
|
||||
const jsxName = jsxElement.openingElement.name;
|
||||
|
@ -34,9 +72,17 @@ export default declare(api => {
|
|||
|
||||
for (const jsxAttribute of jsxElement.openingElement.attributes) {
|
||||
if (t.isJSXAttribute(jsxAttribute)) {
|
||||
if (!t.isJSXIdentifier(jsxAttribute.name))
|
||||
let namespace: t.JSXIdentifier | undefined;
|
||||
let name: t.JSXIdentifier | undefined;
|
||||
if (t.isJSXNamespacedName(jsxAttribute.name)) {
|
||||
namespace = jsxAttribute.name.namespace;
|
||||
name = jsxAttribute.name.name;
|
||||
} else if (t.isJSXIdentifier(jsxAttribute.name)) {
|
||||
name = jsxAttribute.name;
|
||||
}
|
||||
if (!name)
|
||||
continue;
|
||||
const attrName = jsxAttribute.name.name;
|
||||
const attrName = (namespace ? namespace.name + ':' : '') + name.name;
|
||||
if (t.isStringLiteral(jsxAttribute.value))
|
||||
props.push(t.objectProperty(t.stringLiteral(attrName), jsxAttribute.value));
|
||||
else if (t.isJSXExpressionContainer(jsxAttribute.value) && t.isExpression(jsxAttribute.value.expression))
|
||||
|
@ -58,10 +104,10 @@ export default declare(api => {
|
|||
children.push(child.expression);
|
||||
else if (t.isJSXSpreadChild(child))
|
||||
children.push(t.spreadElement(child.expression));
|
||||
|
||||
}
|
||||
|
||||
path.replaceWith(t.objectExpression([
|
||||
t.objectProperty(t.identifier('kind'), t.stringLiteral('jsx')),
|
||||
t.objectProperty(t.identifier('type'), t.stringLiteral(name)),
|
||||
t.objectProperty(t.identifier('props'), t.objectExpression(props)),
|
||||
t.objectProperty(t.identifier('children'), t.arrayExpression(children)),
|
||||
|
@ -70,3 +116,26 @@ export default declare(api => {
|
|||
}
|
||||
};
|
||||
});
|
||||
|
||||
function flushConst(importPath: NodePath<t.ImportDeclaration>, keepPath: boolean) {
|
||||
const importNode = importPath.node;
|
||||
const importNodes = (importPath.parentPath.node as t.Program).body.filter(i => t.isImportDeclaration(i));
|
||||
const isLast = importNodes.indexOf(importNode) === importNodes.length - 1;
|
||||
if (!isLast) {
|
||||
if (!keepPath)
|
||||
importPath.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const components = importPath.parentPath.getData('pw-components');
|
||||
if (!components.size)
|
||||
return;
|
||||
const variables = [];
|
||||
for (const [key, value] of components)
|
||||
variables.push(t.variableDeclarator(t.identifier(key), t.stringLiteral(value)));
|
||||
importPath.skip();
|
||||
if (keepPath)
|
||||
importPath.replaceWithMultiple([importNode, t.variableDeclaration('const', variables)]);
|
||||
else
|
||||
importPath.replaceWith(t.variableDeclaration('const', variables));
|
||||
}
|
||||
|
|
|
@ -176,6 +176,7 @@ const webPackFiles = [
|
|||
'packages/playwright-core/src/web/traceViewer/webpack-sw.config.js',
|
||||
'packages/playwright-core/src/web/recorder/webpack.config.js',
|
||||
'packages/html-reporter/webpack.config.js',
|
||||
'packages/html-reporter/playwright-ct/webpack.config.js',
|
||||
];
|
||||
for (const file of webPackFiles) {
|
||||
steps.push({
|
||||
|
|
|
@ -171,6 +171,21 @@ const workspace = new Workspace(ROOT_PATH, [
|
|||
path: path.join(ROOT_PATH, 'packages', 'html-reporter'),
|
||||
files: [],
|
||||
}),
|
||||
new PWPackage({
|
||||
name: '@playwright/ct-react',
|
||||
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-react'),
|
||||
files: [],
|
||||
}),
|
||||
new PWPackage({
|
||||
name: '@playwright/ct-svelte',
|
||||
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-svelte'),
|
||||
files: [],
|
||||
}),
|
||||
new PWPackage({
|
||||
name: '@playwright/ct-vue',
|
||||
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-vue'),
|
||||
files: [],
|
||||
}),
|
||||
]);
|
||||
|
||||
if (require.main === module) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче