diff --git a/package-lock.json b/package-lock.json index dc6ee151dd..3c5493c8c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5770,7 +5770,6 @@ "version": "3.49.0", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz", "integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==", - "peer": true, "engines": { "node": ">= 8" } @@ -6490,6 +6489,9 @@ "@sveltejs/vite-plugin-svelte": "^1.0.1", "vite": "^3.0.0" }, + "devDependencies": { + "svelte": "^3.49.0" + }, "engines": { "node": ">=14" } @@ -7485,6 +7487,7 @@ "requires": { "@playwright/test": "1.25.0-next", "@sveltejs/vite-plugin-svelte": "^1.0.1", + "svelte": "*", "vite": "^3.0.0" }, "dependencies": { @@ -10959,8 +10962,7 @@ "svelte": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz", - "integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==", - "peer": true + "integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==" }, "svelte-hmr": { "version": "0.14.12", diff --git a/packages/playwright-ct-react/index.d.ts b/packages/playwright-ct-react/index.d.ts index 4f89f7b643..3ed9f5083f 100644 --- a/packages/playwright-ct-react/index.d.ts +++ b/packages/playwright-ct-react/index.d.ts @@ -36,6 +36,7 @@ export type PlaywrightTestConfig = Omit & { export interface ComponentFixtures { mount(component: JSX.Element, options?: { hooksConfig?: any }): Promise; + unmount(component: Locator): Promise; } export const test: TestType< diff --git a/packages/playwright-ct-react/registerSource.mjs b/packages/playwright-ct-react/registerSource.mjs index 6d23f88373..644d80f1f5 100644 --- a/packages/playwright-ct-react/registerSource.mjs +++ b/packages/playwright-ct-react/registerSource.mjs @@ -77,3 +77,8 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => { for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || []) await hook({ hooksConfig }); }; + +window.playwrightUnmount = async (element, rootElement) => { + if (!ReactDOM.unmountComponentAtNode(rootElement)) + throw new Error('Component was not mounted'); +}; diff --git a/packages/playwright-ct-svelte/hooks.d.ts b/packages/playwright-ct-svelte/hooks.d.ts index 36ac2620fa..958eea4342 100644 --- a/packages/playwright-ct-svelte/hooks.d.ts +++ b/packages/playwright-ct-svelte/hooks.d.ts @@ -14,5 +14,7 @@ * limitations under the License. */ +import type { SvelteComponent } from "svelte"; + export declare function beforeMount(callback: (params: { hooksConfig: any }) => Promise): void; -export declare function afterMount(callback: (params: { hooksConfig: any }) => Promise): void; +export declare function afterMount(callback: (params: { hooksConfig: any, svelteComponent: SvelteComponent }) => Promise): void; diff --git a/packages/playwright-ct-svelte/index.d.ts b/packages/playwright-ct-svelte/index.d.ts index 46289cb2ec..33f5508099 100644 --- a/packages/playwright-ct-svelte/index.d.ts +++ b/packages/playwright-ct-svelte/index.d.ts @@ -47,6 +47,7 @@ interface ComponentFixtures { on?: { [key: string]: Function }, hooksConfig?: any, }): Promise; + unmount(component: Locator): Promise; } export const test: TestType< diff --git a/packages/playwright-ct-svelte/package.json b/packages/playwright-ct-svelte/package.json index 021138ba19..7bba196454 100644 --- a/packages/playwright-ct-svelte/package.json +++ b/packages/playwright-ct-svelte/package.json @@ -26,8 +26,11 @@ } }, "dependencies": { - "@sveltejs/vite-plugin-svelte": "^1.0.1", "@playwright/test": "1.25.0-next", + "@sveltejs/vite-plugin-svelte": "^1.0.1", "vite": "^3.0.0" + }, + "devDependencies": { + "svelte": "^3.49.0" } } diff --git a/packages/playwright-ct-svelte/registerSource.mjs b/packages/playwright-ct-svelte/registerSource.mjs index e7b9c1d55b..40ffd5aa98 100644 --- a/packages/playwright-ct-svelte/registerSource.mjs +++ b/packages/playwright-ct-svelte/registerSource.mjs @@ -20,6 +20,7 @@ /** @typedef {import('../playwright-test/types/component').Component} Component */ /** @typedef {any} FrameworkComponent */ +/** @typedef {import('svelte').SvelteComponent} SvelteComponent */ /** @type {Map} */ const registry = new Map(); @@ -54,14 +55,24 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => { for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || []) await hook({ hooksConfig }); - const wrapper = new componentCtor({ + const svelteComponent = /** @type {SvelteComponent} */ (new componentCtor({ target: rootElement, props: component.options?.props, - }); + })); + rootElement[svelteComponentKey] = svelteComponent; for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || []) - await hook({ hooksConfig }); + await hook({ hooksConfig, svelteComponent }); for (const [key, listener] of Object.entries(component.options?.on || {})) - wrapper.$on(key, event => listener(event.detail)); + svelteComponent.$on(key, event => listener(event.detail)); }; + +window.playwrightUnmount = async (element, rootElement) => { + const svelteComponent = /** @type {SvelteComponent} */ (rootElement[svelteComponentKey]); + if (!svelteComponent) + throw new Error('Component was not mounted'); + svelteComponent.$destroy(); +}; + +const svelteComponentKey = Symbol('svelteComponent'); diff --git a/packages/playwright-ct-vue/index.d.ts b/packages/playwright-ct-vue/index.d.ts index f5f4d4906d..5981c4725d 100644 --- a/packages/playwright-ct-vue/index.d.ts +++ b/packages/playwright-ct-vue/index.d.ts @@ -48,6 +48,7 @@ export interface ComponentFixtures { on?: { [key: string]: Function }, hooksConfig?: any, }): Promise; + unmount(locator: Locator): Promise; } export const test: TestType< diff --git a/packages/playwright-ct-vue/registerSource.mjs b/packages/playwright-ct-vue/registerSource.mjs index f0adca1eb9..b33eb0d5b8 100644 --- a/packages/playwright-ct-vue/registerSource.mjs +++ b/packages/playwright-ct-vue/registerSource.mjs @@ -164,6 +164,16 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => { for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || []) await hook({ app, hooksConfig }); const instance = app.mount(rootElement); + instance.$el[appKey] = app; for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || []) await hook({ app, hooksConfig, instance }); }; + +window.playwrightUnmount = async element => { + const app = /** @type {import('vue').App} */ (element[appKey]); + if (!app) + throw new Error('Component was not mounted'); + app.unmount(); +}; + +const appKey = Symbol('appKey'); diff --git a/packages/playwright-ct-vue2/index.d.ts b/packages/playwright-ct-vue2/index.d.ts index f5f4d4906d..5981c4725d 100644 --- a/packages/playwright-ct-vue2/index.d.ts +++ b/packages/playwright-ct-vue2/index.d.ts @@ -48,6 +48,7 @@ export interface ComponentFixtures { on?: { [key: string]: Function }, hooksConfig?: any, }): Promise; + unmount(locator: Locator): Promise; } export const test: TestType< diff --git a/packages/playwright-ct-vue2/registerSource.mjs b/packages/playwright-ct-vue2/registerSource.mjs index ef3a33c9d0..3edb704ab5 100644 --- a/packages/playwright-ct-vue2/registerSource.mjs +++ b/packages/playwright-ct-vue2/registerSource.mjs @@ -135,8 +135,6 @@ function render(component, h) { } window.playwrightMount = async (component, rootElement, hooksConfig) => { - const config = hooksConfig || /** @type {any} */(component).options?.hooksConfig; - for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || []) await hook({ hooksConfig }); @@ -144,7 +142,18 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => { render: h => render(component, h), }).$mount(); rootElement.appendChild(instance.$el); + /** @type {any} */ (instance.$el)[instanceKey] = instance; for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || []) await hook({ hooksConfig, instance }); }; + +window.playwrightUnmount = async element => { + const component = /** @type {any} */(element)[instanceKey]; + if (!component) + throw new Error('Component was not mounted'); + component.$destroy(); + element.remove(); +}; + +const instanceKey = Symbol('instanceKey'); diff --git a/packages/playwright-test/src/mount.ts b/packages/playwright-test/src/mount.ts index 46c2301e16..4cc0cc6326 100644 --- a/packages/playwright-test/src/mount.ts +++ b/packages/playwright-test/src/mount.ts @@ -20,7 +20,10 @@ import type { Component, JsxComponent, ObjectComponentOptions } from '../types/c let boundCallbacksForMount: Function[] = []; export const fixtures: Fixtures< - PlaywrightTestArgs & PlaywrightTestOptions & { mount: (component: any, options: any) => Promise }, + PlaywrightTestArgs & PlaywrightTestOptions & { + mount: (component: any, options: any) => Promise; + unmount: (locator: Locator) => Promise; + }, PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { context: BrowserContext | undefined, hash: string } }, { _contextFactory: (options?: BrowserContextOptions) => Promise, _contextReuseEnabled: boolean }> = { @@ -49,6 +52,15 @@ export const fixtures: Fixtures< }); boundCallbacksForMount = []; }, + + unmount: async ({}, use) => { + await use(async (locator: Locator) => { + await locator.evaluate(async element => { + const rootElement = document.getElementById('root')!; + await window.playwrightUnmount(element, rootElement); + }); + }); + }, }; async function innerMount(page: Page, jsxOrType: JsxComponent | string, options: ObjectComponentOptions = {}): Promise { diff --git a/packages/playwright-test/types/component.d.ts b/packages/playwright-test/types/component.d.ts index b51ad91765..872d1810c8 100644 --- a/packages/playwright-test/types/component.d.ts +++ b/packages/playwright-test/types/component.d.ts @@ -39,5 +39,6 @@ export type Component = JsxComponent | ObjectComponent; declare global { interface Window { playwrightMount(component: Component, rootElement: Element, hooksConfig: any): Promise; + playwrightUnmount(element: Element, rootElement: Element): Promise; } } diff --git a/tests/components/ct-react-vite/src/App.spec.tsx b/tests/components/ct-react-vite/src/App.spec.tsx index 459de3579a..51fbd81716 100644 --- a/tests/components/ct-react-vite/src/App.spec.tsx +++ b/tests/components/ct-react-vite/src/App.spec.tsx @@ -18,3 +18,10 @@ test('should configure app', async ({ page, mount }) => { }); expect(messages).toEqual(['Before mount: {\"route\":\"A\"}', 'After mount']); }); + +test('should unmount', async ({ page, mount, unmount }) => { + const component = await mount(); + await expect(page.locator('#root')).toContainText('Hello Vite + React!'); + await unmount(component); + await expect(page.locator('#root')).not.toContainText('Hello Vite + React!'); +}); diff --git a/tests/components/ct-svelte-vite/package.json b/tests/components/ct-svelte-vite/package.json index fdd58dd79a..1c3b967d94 100644 --- a/tests/components/ct-svelte-vite/package.json +++ b/tests/components/ct-svelte-vite/package.json @@ -12,7 +12,7 @@ "devDependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.1", "@tsconfig/svelte": "^2.0.1", - "svelte": "^3.44.0", + "svelte": "^3.49.0", "svelte-check": "^2.2.7", "svelte-preprocess": "^4.9.8", "tslib": "^2.3.1", @@ -23,4 +23,4 @@ "@playwright/experimental-ct-svelte": "^1.22.2", "@playwright/test": "^1.22.2" } -} \ No newline at end of file +} diff --git a/tests/components/ct-svelte-vite/src/lib/Counter.spec.ts b/tests/components/ct-svelte-vite/src/lib/Counter.spec.ts index f0c2e9920b..f540a3c1e0 100644 --- a/tests/components/ct-svelte-vite/src/lib/Counter.spec.ts +++ b/tests/components/ct-svelte-vite/src/lib/Counter.spec.ts @@ -47,3 +47,14 @@ test('should configure app', async ({ page, mount }) => { }); expect(messages).toEqual(['Before mount: {\"route\":\"A\"}', 'After mount']); }); + +test('should unmount', async ({ page, mount, unmount }) => { + const component = await mount(Counter, { + props: { + suffix: 'my suffix', + }, + }); + await expect(page.locator('#root')).toContainText('my suffix') + await unmount(component); + await expect(page.locator('#root')).not.toContainText('my suffix'); +}); diff --git a/tests/components/ct-vue-vite/src/notation-vue.spec.ts b/tests/components/ct-vue-vite/src/notation-vue.spec.ts index c5b1baee6f..8e946a70da 100644 --- a/tests/components/ct-vue-vite/src/notation-vue.spec.ts +++ b/tests/components/ct-vue-vite/src/notation-vue.spec.ts @@ -78,3 +78,14 @@ test('should run hooks', async ({ page, mount }) => { }) expect(messages).toEqual(['Before mount: {\"route\":\"A\"}, app: true', 'After mount el: HTMLButtonElement']) }) + +test('should unmount', async ({ page, mount, unmount }) => { + const component = await mount(Button, { + props: { + title: 'Submit' + } + }) + await expect(page.locator('#root')).toContainText('Submit') + await unmount(component); + await expect(page.locator('#root')).not.toContainText('Submit'); +}); diff --git a/tests/components/ct-vue2-cli/src/notation-vue.spec.ts b/tests/components/ct-vue2-cli/src/notation-vue.spec.ts index b86a011d6c..e6d6d2a967 100644 --- a/tests/components/ct-vue2-cli/src/notation-vue.spec.ts +++ b/tests/components/ct-vue2-cli/src/notation-vue.spec.ts @@ -72,3 +72,14 @@ test('should run hooks', async ({ page, mount }) => { }) expect(messages).toEqual(['Before mount: {\"route\":\"A\"}', 'After mount el: HTMLButtonElement']) }) + +test('should unmount', async ({ page, mount, unmount }) => { + const component = await mount(Button, { + props: { + title: 'Submit' + } + }) + await expect(page.locator('#root')).toContainText('Submit') + await unmount(component); + await expect(page.locator('#root')).not.toContainText('Submit'); +});