feat(ct): support Vue2 (#14600)

This commit is contained in:
Pavel Feldman 2022-06-02 17:37:43 -07:00 committed by GitHub
parent 94a0d669b6
commit 74b846270b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 2568 additions and 365 deletions

2060
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,13 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @ts-check
// This file is injected into the registry as text, no dependencies are allowed.
import { createApp, setDevtoolsHook, h } from 'vue';
/** @typedef {import('../playwright-test/types/component').Component} Component */
/** @type { Map<string, import('vue').Component> } */
const registry = new Map();
/**
* @param {{[key: string]: import('vue').Component}} components
*/
export function register(components) {
for (const [name, value] of Object.entries(components))
registry.set(name, value);
@ -27,10 +34,25 @@ export function register(components) {
const allListeners = [];
/**
* @param {Component | string} child
* @returns {import('vue').VNode | string}
*/
function renderChild(child) {
return typeof child === 'string' ? child : render(child);
}
/**
* @param {Component} component
* @returns {import('vue').VNode}
*/
function render(component) {
if (typeof component === 'string')
return component;
/**
* @type {import('vue').Component | string | undefined}
*/
let componentFunc = registry.get(component.type);
if (!componentFunc) {
// Lookup by shorthand.
@ -48,20 +70,25 @@ function render(component) {
componentFunc = componentFunc || component.type;
const isVueComponent = componentFunc !== component.type;
/**
* @type {(import('vue').VNode | string)[]}
*/
const children = [];
/** @type {{[key: string]: any}} */
const slots = {};
const listeners = {};
/** @type {{[key: string]: any}} */
let props = {};
if (component.kind === 'jsx') {
for (const child of component.children || []) {
if (child.type === 'template') {
if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') {
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);
slots[slot] = child.children.map(renderChild);
} else {
children.push(render(child));
children.push(renderChild(child));
}
}
@ -100,11 +127,15 @@ function render(component) {
lastArg = children;
}
// @ts-ignore
const wrapper = h(componentFunc, props, lastArg);
allListeners.push([wrapper, listeners]);
return wrapper;
}
/**
* @returns {any}
*/
function createDevTools() {
return {
emit(eventType, ...payload) {
@ -123,7 +154,7 @@ function createDevTools() {
};
}
window.playwrightMount = async component => {
/** @type {any} */ (window).playwrightMount = /** @param {Component} component */ async component => {
if (!document.getElementById('root')) {
const rootElement = document.createElement('div');
rootElement.id = 'root';

View file

@ -0,0 +1,9 @@
**/*
!README.md
!LICENSE
!register.d.ts
!register.mjs
!registerSource.mjs
!index.d.ts
!index.js

View file

@ -0,0 +1,3 @@
> **BEWARE** This package is EXPERIMENTAL and does not respect semver.
Read more at https://playwright.dev/docs/test-components

50
packages/playwright-ct-vue2/index.d.ts vendored Normal file
View file

@ -0,0 +1,50 @@
/**
* 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,
PlaywrightTestConfig as BasePlaywrightTestConfig,
PlaywrightTestOptions,
PlaywrightWorkerArgs,
PlaywrightWorkerOptions,
Locator,
} from '@playwright/test';
import type { InlineConfig } from 'vite';
export type PlaywrightTestConfig = Omit<BasePlaywrightTestConfig, 'use'> & {
use?: BasePlaywrightTestConfig['use'] & {
ctPort?: number,
ctTemplateDir?: string,
ctCacheDir?: string,
ctViteConfig?: InlineConfig
}
};
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, devices } from '@playwright/test';

View file

@ -0,0 +1,31 @@
/**
* 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, devices, _addRunnerPlugin } = require('@playwright/test');
const { fixtures } = require('@playwright/test/lib/mount');
const path = require('path');
_addRunnerPlugin(() => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
return createPlugin(
path.join(__dirname, 'registerSource.mjs'),
() => require('vite-plugin-vue2').createVuePlugin());
});
const test = baseTest.extend(fixtures);
module.exports = { test, expect, devices };

View file

@ -0,0 +1,32 @@
{
"name": "@playwright/experimental-ct-vue2",
"version": "1.23.0-next",
"description": "Playwright Component Testing for Vue2",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
"engines": {
"node": ">=14"
},
"author": {
"name": "Microsoft Corporation"
},
"license": "Apache-2.0",
"exports": {
".": {
"types": "./index.d.ts",
"default": "./index.js"
},
"./register": {
"types": "./register.d.ts",
"default": "./register.mjs"
}
},
"dependencies": {
"@playwright/test": "1.23.0-next",
"vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.1"
},
"devDependencies": {
"vue": "^2.6.14"
}
}

View file

@ -0,0 +1,24 @@
/**
* 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 default function register(
components: { [key: string]: any },
options?: {
createApp: any,
setDevtoolsHook: any,
h: any,
}
): void

View file

@ -0,0 +1,21 @@
/**
* 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 { register } from './registerSource.mjs';
export default components => {
register(components);
};

View file

@ -0,0 +1,148 @@
/**
* 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.
*/
// @ts-check
// This file is injected into the registry as text, no dependencies are allowed.
import Vue from 'vue';
/** @typedef {import('../playwright-test/types/component').Component} Component */
/** @type { Map<string, import('vue').Component> } */
const registry = new Map();
/**
* @param {{[key: string]: import('vue').Component}} components
*/
export function register(components) {
for (const [name, value] of Object.entries(components))
registry.set(name, value);
}
/**
* @param {Component | string} child
* @param {import('vue').CreateElement} h
* @returns {import('vue').VNode | string}
*/
function renderChild(child, h) {
return typeof child === 'string' ? child : render(child, h);
}
/**
* @param {Component} component
* @param {import('vue').CreateElement} h
* @returns {import('vue').VNode}
*/
function render(component, h) {
/**
* @type {import('vue').Component | string | undefined}
*/
let componentFunc = registry.get(component.type);
if (!componentFunc) {
// Lookup by shorthand.
for (const [name, value] of registry) {
if (component.type.endsWith(`_${name}_vue`)) {
componentFunc = value;
break;
}
}
}
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...registry.keys()]}`);
componentFunc = componentFunc || component.type;
const isVueComponent = componentFunc !== component.type;
/**
* @type {(import('vue').VNode | string)[]}
*/
const children = [];
/** @type {import('vue').VNodeData} */
const nodeData = {};
nodeData.attrs = {};
nodeData.props = {};
nodeData.scopedSlots = {};
nodeData.on = {};
if (component.kind === 'jsx') {
for (const child of component.children || []) {
if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') {
const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:'));
const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default';
nodeData.scopedSlots[slot] = () => child.children.map(c => renderChild(c, h));
} else {
children.push(renderChild(child, h));
}
}
for (const [key, value] of Object.entries(component.props)) {
if (key.startsWith('v-on:')) {
const event = key.substring('v-on:'.length);
nodeData.on[event] = value;
} else {
if (isVueComponent)
nodeData.props[key] = value;
else
nodeData.attrs[key] = value;
}
}
}
if (component.kind === 'object') {
// Vue test util syntax.
const options = component.options || {};
for (const [key, value] of Object.entries(options.slots || {})) {
const list = (Array.isArray(value) ? value : [value]).map(v => renderChild(v, h));
if (key === 'default')
children.push(...list);
else
nodeData.scopedSlots[key] = () => list;
}
nodeData.props = options.props || {};
for (const [key, value] of Object.entries(options.on || {}))
nodeData.on[key] = value;
}
/** @type {(string|import('vue').VNode)[] | undefined} */
let lastArg;
if (Object.entries(nodeData.scopedSlots).length) {
if (children.length)
nodeData.scopedSlots.default = () => children;
} else if (children.length) {
lastArg = children;
}
const wrapper = h(componentFunc, nodeData, lastArg);
return wrapper;
}
/** @type {any} */ (window).playwrightMount = /** @param {Component} component */ async component => {
let rootElement = document.getElementById('root');
if (!rootElement) {
rootElement = document.createElement('div');
rootElement.id = 'root';
document.body.append(rootElement);
}
const mounted = new Vue({
render: h => render(component, h),
}).$mount();
rootElement.appendChild(mounted.$el);
return '#root > *';
};

View file

@ -23,11 +23,11 @@ import { parse, traverse, types as t } from '../babelBundle';
import type { ComponentInfo } from '../tsxTransform';
import { collectComponentUsages, componentInfo } from '../tsxTransform';
import type { FullConfig } from '../types';
import { assert } from 'playwright-core/lib/utils';
import { assert, calculateSha1 } from 'playwright-core/lib/utils';
import type { AddressInfo } from 'net';
let previewServer: PreviewServer;
const VERSION = 3;
const VERSION = 4;
type CtConfig = {
ctPort?: number;
@ -58,13 +58,19 @@ export function createPlugin(
const buildInfoFile = path.join(outDir, 'metainfo.json');
let buildExists = false;
let buildInfo: BuildInfo;
const registerSource = await fs.promises.readFile(registerSourceFile, 'utf-8');
const registerSourceHash = calculateSha1(registerSource);
try {
buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo;
assert(buildInfo.version === VERSION);
assert(buildInfo.registerSourceHash === registerSourceHash);
buildExists = true;
} catch (e) {
buildInfo = {
version: VERSION,
registerSourceHash,
components: [],
tests: {},
sources: {},
@ -106,7 +112,6 @@ export function createPlugin(
viteConfig.plugins = viteConfig.plugins || [
frameworkPluginFactory()
];
const registerSource = await fs.promises.readFile(registerSourceFile, 'utf-8');
viteConfig.plugins.push(vitePlugin(registerSource, relativeTemplateDir, buildInfo, componentRegistry));
viteConfig.configFile = viteConfig.configFile || false;
viteConfig.define = viteConfig.define || {};
@ -149,6 +154,7 @@ export function createPlugin(
type BuildInfo = {
version: number,
registerSourceHash: string,
sources: {
[key: string]: {
timestamp: number;

View file

@ -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.
*/
export type JsxComponent = {
kind: 'jsx',
type: string,
props: {[key: string]: any},
children: (Component | string)[],
};
export type ObjectComponent = {
kind: 'object',
type: string,
options?: {
props?: { [key: string]: any },
slots?: { [key: string]: any },
on?: { [key: string]: Function },
}
};
export type Component = JsxComponent | ObjectComponent;

View file

@ -19,7 +19,7 @@
"vite": "^2.8.0"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-react": "0.0.1",
"@playwright/test": "1.21.0-alpha-mar-12-2022"
"@playwright/experimental-ct-react": "^1.22.2",
"@playwright/test": "^1.22.2"
}
}

View file

@ -16,8 +16,8 @@
"typescript": "^4.6.2"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-react": "0.0.1",
"@playwright/test": "1.21.0-alpha-mar-12-2022"
"@playwright/experimental-ct-react": "^1.22.2",
"@playwright/test": "^1.22.2"
},
"scripts": {
"start": "react-scripts start",

View file

@ -14,8 +14,8 @@
"svelte": "^3.44.0"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-svelte": "0.0.1",
"@playwright/test": "1.21.0-alpha-mar-12-2022"
"@playwright/experimental-ct-svelte": "^1.22.2",
"@playwright/test": "^1.22.2"
},
"type": "module"
}

View file

@ -20,7 +20,7 @@
"vite": "^2.8.0"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-svelte": "0.0.1",
"@playwright/test": "1.21.0-alpha-mar-12-2022"
"@playwright/experimental-ct-svelte": "^1.22.2",
"@playwright/test": "^1.22.2"
}
}

View file

@ -18,8 +18,8 @@
"svelte": "^3.0.0"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-svelte": "0.0.1",
"@playwright/test": "1.21.0-alpha-mar-12-2022"
"@playwright/experimental-ct-svelte": "^1.22.2",
"@playwright/test": "^1.22.2"
},
"dependencies": {
"sirv-cli": "^2.0.0"

View file

@ -21,8 +21,8 @@
"eslint-plugin-vue": "^8.0.3"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-vue": "0.0.1",
"@playwright/test": "1.21.0-alpha-mar-12-2022"
"@playwright/experimental-ct-vue": "^1.22.2",
"@playwright/test": "^1.22.2"
},
"eslintConfig": {
"root": true,

View file

@ -14,7 +14,7 @@
"vite": "^2.8.4"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-vue": "0.0.1",
"@playwright/test": "1.21.0-alpha-mar-12-2022"
"@playwright/experimental-ct-vue": "^1.22.2",
"@playwright/test": "^1.22.2"
}
}

23
tests/components/ct-vue2-cli/.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,24 @@
# ct-vue2-cli
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View file

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View file

@ -0,0 +1,47 @@
{
"name": "ct-vue2-cli",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^2.6.14"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14"
},
"@standaloneDevDependencies": {
"@playwright/experimental-ct-vue2": "^1.22.2",
"@playwright/test": "^1.22.2"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

View file

@ -0,0 +1,43 @@
/**
* 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 PlaywrightTestConfig, devices } from '@playwright/experimental-ct-vue2';
const config: PlaywrightTestConfig = {
testDir: 'src',
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: 'html',
use: {
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
};
export default config;

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<div id="root"></div>
<script type="module" src="/playwright/index.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View file

@ -0,0 +1,28 @@
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,12 @@
<template>
<button @click='$emit("submit", "hello")'>
{{ title }}
</button>
</template>
<script>
export default {
name: 'ButtonButton',
props: ['title']
}
</script>

View file

@ -0,0 +1,11 @@
<template>
<div>
<h1>Welcome!</h1>
<main>
<slot />
</main>
<footer>
Thanks for visiting.
</footer>
</div>
</template>

View file

@ -0,0 +1,58 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View file

@ -0,0 +1,14 @@
<template>
<div>
<header>
<slot name="header" />
</header>
<main>
<slot name="main" />
</main>
<footer>
<slot name="footer" />
</footer>
</div>
</template>

View file

@ -0,0 +1,8 @@
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')

View file

@ -0,0 +1,62 @@
import { test, expect } from '@playwright/experimental-ct-vue2'
import Button from './components/Button.vue'
import DefaultSlot from './components/DefaultSlot.vue'
import NamedSlots from './components/NamedSlots.vue'
test.use({ viewport: { width: 500, height: 500 } })
test('props should work', async ({ mount }) => {
const component = await mount(<Button title='Submit'></Button>)
await expect(component).toContainText('Submit')
})
test('event should work', async ({ mount }) => {
const messages = []
const component = await mount(<Button title='Submit' v-on:submit={data => {
messages.push(data)
}}></Button>)
await component.click()
expect(messages).toEqual(['hello'])
})
test('default slot should work', async ({ mount }) => {
const component = await mount(<DefaultSlot>
Main Content
</DefaultSlot>)
await expect(component).toContainText('Main Content')
})
test('multiple slots should work', async ({ mount }) => {
const component = await mount(<DefaultSlot>
<div id="one">One</div>
<div id="two">Two</div>
</DefaultSlot>)
await expect(component.locator('#one')).toContainText('One')
await expect(component.locator('#two')).toContainText('Two')
})
test('named slots should work', async ({ mount }) => {
const component = await mount(<NamedSlots>
<template v-slot:header>
Header
</template>
<template v-slot:main>
Main Content
</template>
<template v-slot:footer>
Footer
</template>
</NamedSlots>);
await expect(component).toContainText('Header')
await expect(component).toContainText('Main Content')
await expect(component).toContainText('Footer')
})
test('slot should emit events', async ({ mount }) => {
let clickFired = false;
const component = await mount(<DefaultSlot>
<span v-on:click={() => clickFired = true}>Main Content</span>
</DefaultSlot>);
await component.locator('text=Main Content').click();
expect(clickFired).toBeTruthy();
})

View file

@ -0,0 +1,62 @@
import { test, expect } from '@playwright/experimental-ct-vue2'
import Button from './components/Button.vue'
import DefaultSlot from './components/DefaultSlot.vue'
import NamedSlots from './components/NamedSlots.vue'
test.use({ viewport: { width: 500, height: 500 } })
test('props should work', async ({ mount }) => {
const component = await mount(Button, {
props: {
title: 'Submit'
}
})
await expect(component).toContainText('Submit')
})
test('event should work', async ({ mount }) => {
const messages = []
const component = await mount(Button, {
props: {
title: 'Submit'
},
on: {
submit: data => messages.push(data)
}
})
await component.click()
expect(messages).toEqual(['hello'])
})
test('default slot should work', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: 'Main Content'
}
})
await expect(component).toContainText('Main Content')
})
test('multiple slots should work', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: ['one', 'two']
}
})
await expect(component).toContainText('one')
await expect(component).toContainText('two')
})
test('named slots should work', async ({ mount }) => {
const component = await mount(NamedSlots, {
slots: {
header: 'Header',
main: 'Main Content',
footer: 'Footer'
}
})
await expect(component).toContainText('Header')
await expect(component).toContainText('Main Content')
await expect(component).toContainText('Footer')
})

View file

@ -0,0 +1,4 @@
declare module '*.vue' {
const value: any;
export default value;
}

View file

@ -0,0 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})

View file

@ -4,6 +4,7 @@ const path = require('path');
const rimraf = require('rimraf');
for (const pkg of workspace.packages()) {
rimraf.sync(path.join(pkg.path, 'node_modules'));
rimraf.sync(path.join(pkg.path, 'lib'));
rimraf.sync(path.join(pkg.path, 'src', 'generated'));
const bundles = path.join(pkg.path, 'bundles');

View file

@ -193,6 +193,11 @@ const workspace = new Workspace(ROOT_PATH, [
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-vue'),
files: ['LICENSE'],
}),
new PWPackage({
name: '@playwright/experimental-ct-vue2',
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-vue2'),
files: ['LICENSE'],
}),
]);
if (require.main === module) {