mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(ct-react): Support React 18 only (#19814)
BREAKING CHANGE: Drop support for React 17 and earlier Support for React 17 an earlier is provided by `@playwright/experimental-ct-react-17` Closes #19923
This commit is contained in:
		
							parent
							
								
									be259dac7c
								
							
						
					
					
						commit
						fbaf56a13f
					
				
							
								
								
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1297,6 +1297,10 @@ | |||||||
|       "resolved": "packages/playwright-ct-react", |       "resolved": "packages/playwright-ct-react", | ||||||
|       "link": true |       "link": true | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@playwright/experimental-ct-react17": { | ||||||
|  |       "resolved": "packages/playwright-ct-react17", | ||||||
|  |       "link": true | ||||||
|  |     }, | ||||||
|     "node_modules/@playwright/experimental-ct-solid": { |     "node_modules/@playwright/experimental-ct-solid": { | ||||||
|       "resolved": "packages/playwright-ct-solid", |       "resolved": "packages/playwright-ct-solid", | ||||||
|       "link": true |       "link": true | ||||||
| @ -5990,6 +5994,22 @@ | |||||||
|         "node": ">=14" |         "node": ">=14" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "packages/playwright-ct-react17": { | ||||||
|  |       "name": "@playwright/experimental-ct-react17", | ||||||
|  |       "version": "1.32.0-next", | ||||||
|  |       "license": "Apache-2.0", | ||||||
|  |       "dependencies": { | ||||||
|  |         "@playwright/test": "1.32.0-next", | ||||||
|  |         "@vitejs/plugin-react": "^3.1.0", | ||||||
|  |         "vite": "^4.1.1" | ||||||
|  |       }, | ||||||
|  |       "bin": { | ||||||
|  |         "playwright": "cli.js" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=14" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "packages/playwright-ct-solid": { |     "packages/playwright-ct-solid": { | ||||||
|       "name": "@playwright/experimental-ct-solid", |       "name": "@playwright/experimental-ct-solid", | ||||||
|       "version": "1.32.0-next", |       "version": "1.32.0-next", | ||||||
| @ -6908,6 +6928,14 @@ | |||||||
|         "vite": "^4.1.1" |         "vite": "^4.1.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "@playwright/experimental-ct-react17": { | ||||||
|  |       "version": "file:packages/playwright-ct-react17", | ||||||
|  |       "requires": { | ||||||
|  |         "@playwright/test": "1.32.0-next", | ||||||
|  |         "@vitejs/plugin-react": "^3.1.0", | ||||||
|  |         "vite": "^4.1.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "@playwright/experimental-ct-solid": { |     "@playwright/experimental-ct-solid": { | ||||||
|       "version": "file:packages/playwright-ct-solid", |       "version": "file:packages/playwright-ct-solid", | ||||||
|       "requires": { |       "requires": { | ||||||
|  | |||||||
| @ -84,6 +84,8 @@ test('should show the project names', async ({ mount }) => { | |||||||
|     > |     > | ||||||
|     </HeaderView>); |     </HeaderView>); | ||||||
|     await expect(component.getByText('Project: my-project')).toBeVisible(); |     await expect(component.getByText('Project: my-project')).toBeVisible(); | ||||||
|  | 
 | ||||||
|  |     await component.unmount(); | ||||||
|   }); |   }); | ||||||
|   await test.step('with 1 project and empty projectName', async () => { |   await test.step('with 1 project and empty projectName', async () => { | ||||||
|     const component = await mount(<HeaderView |     const component = await mount(<HeaderView | ||||||
| @ -94,6 +96,8 @@ test('should show the project names', async ({ mount }) => { | |||||||
|     > |     > | ||||||
|     </HeaderView>); |     </HeaderView>); | ||||||
|     await expect(component.getByText('Project:')).toBeHidden(); |     await expect(component.getByText('Project:')).toBeHidden(); | ||||||
|  | 
 | ||||||
|  |     await component.unmount(); | ||||||
|   }); |   }); | ||||||
|   await test.step('with more than 1 project', async () => { |   await test.step('with more than 1 project', async () => { | ||||||
|     const component = await mount(<HeaderView |     const component = await mount(<HeaderView | ||||||
| @ -105,5 +109,7 @@ test('should show the project names', async ({ mount }) => { | |||||||
|     </HeaderView>); |     </HeaderView>); | ||||||
|     await expect(component.getByText('my-project')).toBeHidden(); |     await expect(component.getByText('my-project')).toBeHidden(); | ||||||
|     await expect(component.getByText('great-project')).toBeHidden(); |     await expect(component.getByText('great-project')).toBeHidden(); | ||||||
|  | 
 | ||||||
|  |     await component.unmount(); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -17,14 +17,16 @@ | |||||||
| // @ts-check
 | // @ts-check
 | ||||||
| // This file is injected into the registry as text, no dependencies are allowed.
 | // This file is injected into the registry as text, no dependencies are allowed.
 | ||||||
| 
 | 
 | ||||||
| import React from 'react'; | import * as React from 'react'; | ||||||
| import ReactDOM from 'react-dom'; | import { createRoot } from 'react-dom/client'; | ||||||
| 
 | 
 | ||||||
| /** @typedef {import('../playwright-test/types/component').Component} Component */ | /** @typedef {import('../playwright-test/types/component').Component} Component */ | ||||||
| /** @typedef {import('react').FunctionComponent} FrameworkComponent */ | /** @typedef {import('react').FunctionComponent} FrameworkComponent */ | ||||||
| 
 | 
 | ||||||
| /** @type {Map<string, FrameworkComponent>} */ | /** @type {Map<string, FrameworkComponent>} */ | ||||||
| const registry = new Map(); | const registry = new Map(); | ||||||
|  | /** @type {Map<Element, import('react-dom/client').Root>>} */ | ||||||
|  | const rootRegistry = new Map(); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {{[key: string]: FrameworkComponent}} components |  * @param {{[key: string]: FrameworkComponent}} components | ||||||
| @ -79,17 +81,33 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => { | |||||||
|       App = () => wrapper; |       App = () => wrapper; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ReactDOM.render(App(), rootElement); |   if (rootRegistry.has(rootElement)) { | ||||||
|  |     throw new Error( | ||||||
|  |         'Attempting to mount a component into an container that already has a React root' | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const root = createRoot(rootElement); | ||||||
|  |   rootRegistry.set(rootElement, root); | ||||||
|  |   root.render(App()); | ||||||
| 
 | 
 | ||||||
|   for (const hook of window.__pw_hooks_after_mount || []) |   for (const hook of window.__pw_hooks_after_mount || []) | ||||||
|     await hook({ hooksConfig }); |     await hook({ hooksConfig }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| window.playwrightUnmount = async rootElement => { | window.playwrightUnmount = async rootElement => { | ||||||
|   if (!ReactDOM.unmountComponentAtNode(rootElement)) |   const root = rootRegistry.get(rootElement); | ||||||
|  |   if (root === undefined) | ||||||
|     throw new Error('Component was not mounted'); |     throw new Error('Component was not mounted'); | ||||||
|  | 
 | ||||||
|  |   root.unmount(); | ||||||
|  |   rootRegistry.delete(rootElement); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| window.playwrightUpdate = async (rootElement, component) => { | window.playwrightUpdate = async (rootElement, component) => { | ||||||
|   ReactDOM.render(render(/** @type {Component} */(component)), rootElement); |   const root = rootRegistry.get(rootElement); | ||||||
|  |   if (root === undefined) | ||||||
|  |     throw new Error('Component was not mounted'); | ||||||
|  | 
 | ||||||
|  |   root.render(render(/** @type {Component} */ (component))); | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								packages/playwright-ct-react17/.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/playwright-ct-react17/.npmignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | **/* | ||||||
|  | 
 | ||||||
|  | !README.md | ||||||
|  | !LICENSE | ||||||
|  | !cli.js | ||||||
|  | !register.d.ts | ||||||
|  | !register.mjs | ||||||
|  | !registerSource.mjs | ||||||
|  | !index.d.ts | ||||||
|  | !index.js | ||||||
|  | !hooks.d.ts | ||||||
|  | !hooks.mjs | ||||||
							
								
								
									
										3
									
								
								packages/playwright-ct-react17/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/playwright-ct-react17/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | > **BEWARE** This package is EXPERIMENTAL and does not respect semver. | ||||||
|  | 
 | ||||||
|  | Read more at https://playwright.dev/docs/test-components | ||||||
							
								
								
									
										17
									
								
								packages/playwright-ct-react17/cli.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								packages/playwright-ct-react17/cli.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | /** | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | module.exports = require('playwright-core/cli'); | ||||||
							
								
								
									
										26
									
								
								packages/playwright-ct-react17/hooks.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								packages/playwright-ct-react17/hooks.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | /** | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | type JsonPrimitive = string | number | boolean | null; | ||||||
|  | type JsonValue = JsonPrimitive | JsonObject | JsonArray; | ||||||
|  | type JsonArray = JsonValue[]; | ||||||
|  | type JsonObject = { [Key in string]?: JsonValue }; | ||||||
|  | export declare function beforeMount<HooksConfig extends JsonObject>( | ||||||
|  |   callback: (params: { hooksConfig: HooksConfig; App: () => JSX.Element }) => Promise<void | JSX.Element> | ||||||
|  | ): void; | ||||||
|  | export declare function afterMount<HooksConfig extends JsonObject>( | ||||||
|  |   callback: (params: { hooksConfig: HooksConfig }) => Promise<void> | ||||||
|  | ): void; | ||||||
							
								
								
									
										29
									
								
								packages/playwright-ct-react17/hooks.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								packages/playwright-ct-react17/hooks.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | /** | ||||||
|  |  * 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 __pw_hooks_before_mount = []; | ||||||
|  | const __pw_hooks_after_mount = []; | ||||||
|  | 
 | ||||||
|  | window.__pw_hooks_before_mount = __pw_hooks_before_mount; | ||||||
|  | window.__pw_hooks_after_mount = __pw_hooks_after_mount; | ||||||
|  | 
 | ||||||
|  | export const beforeMount = callback => { | ||||||
|  |   __pw_hooks_before_mount.push(callback); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const afterMount = callback => { | ||||||
|  |   __pw_hooks_after_mount.push(callback); | ||||||
|  | }; | ||||||
							
								
								
									
										70
									
								
								packages/playwright-ct-react17/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								packages/playwright-ct-react17/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | |||||||
|  | /** | ||||||
|  |  * 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<T = {}, W = {}> = Omit<BasePlaywrightTestConfig<T, W>, 'use'> & { | ||||||
|  |   use?: BasePlaywrightTestConfig<T, W>['use'] & { | ||||||
|  |     ctPort?: number; | ||||||
|  |     ctTemplateDir?: string; | ||||||
|  |     ctCacheDir?: string; | ||||||
|  |     ctViteConfig?: InlineConfig | (() => Promise<InlineConfig>); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type JsonPrimitive = string | number | boolean | null; | ||||||
|  | type JsonValue = JsonPrimitive | JsonObject | JsonArray; | ||||||
|  | type JsonArray = JsonValue[]; | ||||||
|  | type JsonObject = { [Key in string]?: JsonValue }; | ||||||
|  | 
 | ||||||
|  | export interface MountOptions<HooksConfig extends JsonObject> { | ||||||
|  |   hooksConfig?: HooksConfig; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MountResult extends Locator { | ||||||
|  |   unmount(): Promise<void>; | ||||||
|  |   update(component: JSX.Element): Promise<void>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface ComponentFixtures { | ||||||
|  |   mount<HooksConfig extends JsonObject>( | ||||||
|  |     component: JSX.Element, | ||||||
|  |     options?: MountOptions<HooksConfig> | ||||||
|  |   ): Promise<MountResult>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const test: TestType< | ||||||
|  |   PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures, | ||||||
|  |   PlaywrightWorkerArgs & PlaywrightWorkerOptions | ||||||
|  | >; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Defines Playwright config | ||||||
|  |  */ | ||||||
|  | export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; | ||||||
|  | export function defineConfig<T>(config: PlaywrightTestConfig<T>): PlaywrightTestConfig<T>; | ||||||
|  | export function defineConfig<T, W>(config: PlaywrightTestConfig<T, W>): PlaywrightTestConfig<T, W>; | ||||||
|  | 
 | ||||||
|  | export { expect, devices } from '@playwright/test'; | ||||||
							
								
								
									
										31
									
								
								packages/playwright-ct-react17/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/playwright-ct-react17/index.js
									
									
									
									
									
										Normal 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, defineConfig: originalDefineConfig } = require('@playwright/test'); | ||||||
|  | const { fixtures } = require('@playwright/test/lib/mount'); | ||||||
|  | const path = require('path'); | ||||||
|  | 
 | ||||||
|  | const plugin = () => { | ||||||
|  |   // Only fetch upon request to avoid resolution in workers.
 | ||||||
|  |   const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin'); | ||||||
|  |   return createPlugin( | ||||||
|  |     path.join(__dirname, 'registerSource.mjs'), | ||||||
|  |     () => import('@vitejs/plugin-react').then(plugin => plugin.default())); | ||||||
|  | }; | ||||||
|  | const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] }); | ||||||
|  | const test = baseTest.extend(fixtures); | ||||||
|  | 
 | ||||||
|  | module.exports = { test, expect, devices, defineConfig }; | ||||||
							
								
								
									
										36
									
								
								packages/playwright-ct-react17/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								packages/playwright-ct-react17/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | { | ||||||
|  |   "name": "@playwright/experimental-ct-react17", | ||||||
|  |   "version": "1.32.0-next", | ||||||
|  |   "description": "Playwright Component Testing for React", | ||||||
|  |   "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" | ||||||
|  |     }, | ||||||
|  |     "./hooks": { | ||||||
|  |       "types": "./hooks.d.ts", | ||||||
|  |       "default": "./hooks.mjs" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "@vitejs/plugin-react": "^3.1.0", | ||||||
|  |     "@playwright/test": "1.32.0-next", | ||||||
|  |     "vite": "^4.1.1" | ||||||
|  |   }, | ||||||
|  |   "bin": { | ||||||
|  |     "playwright": "./cli.js" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								packages/playwright-ct-react17/register.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								packages/playwright-ct-react17/register.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | /** | ||||||
|  |  * 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 }, | ||||||
|  | ): void | ||||||
							
								
								
									
										21
									
								
								packages/playwright-ct-react17/register.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								packages/playwright-ct-react17/register.mjs
									
									
									
									
									
										Normal 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); | ||||||
|  | }; | ||||||
							
								
								
									
										93
									
								
								packages/playwright-ct-react17/registerSource.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								packages/playwright-ct-react17/registerSource.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | /** | ||||||
|  |  * 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 React from 'react'; | ||||||
|  | import ReactDOM from 'react-dom'; | ||||||
|  | 
 | ||||||
|  | /** @typedef {import('../playwright-test/types/component').Component} Component */ | ||||||
|  | /** @typedef {import('react').FunctionComponent} FrameworkComponent */ | ||||||
|  | 
 | ||||||
|  | /** @type {Map<string, FrameworkComponent>} */ | ||||||
|  | const registry = new Map(); | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {{[key: string]: FrameworkComponent}} components | ||||||
|  |  */ | ||||||
|  | export function register(components) { | ||||||
|  |   for (const [name, value] of Object.entries(components)) | ||||||
|  |     registry.set(name, value); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {Component} component | ||||||
|  |  * @returns {JSX.Element} | ||||||
|  |  */ | ||||||
|  | function render(component) { | ||||||
|  |   let componentFunc = registry.get(component.type); | ||||||
|  |   if (!componentFunc) { | ||||||
|  |     // Lookup by shorthand.
 | ||||||
|  |     for (const [name, value] of registry) { | ||||||
|  |       if (component.type.endsWith(`_${name}`)) { | ||||||
|  |         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()]}`); | ||||||
|  | 
 | ||||||
|  |   const componentFuncOrString = componentFunc || component.type; | ||||||
|  | 
 | ||||||
|  |   if (component.kind !== 'jsx') | ||||||
|  |     throw new Error('Object mount notation is not supported'); | ||||||
|  | 
 | ||||||
|  |   return React.createElement(componentFuncOrString, component.props, ...component.children.map(child => { | ||||||
|  |     if (typeof child === 'string') | ||||||
|  |       return child; | ||||||
|  |     return render(child); | ||||||
|  |   }).filter(child => { | ||||||
|  |     if (typeof child === 'string') | ||||||
|  |       return !!child.trim(); | ||||||
|  |     return true; | ||||||
|  |   })); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | window.playwrightMount = async (component, rootElement, hooksConfig) => { | ||||||
|  |   let App = () => render(component); | ||||||
|  |   for (const hook of window.__pw_hooks_before_mount || []) { | ||||||
|  |     const wrapper = await hook({ App, hooksConfig }); | ||||||
|  |     if (wrapper) | ||||||
|  |       App = () => wrapper; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ReactDOM.render(App(), rootElement); | ||||||
|  | 
 | ||||||
|  |   for (const hook of window.__pw_hooks_after_mount || []) | ||||||
|  |     await hook({ hooksConfig }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | window.playwrightUnmount = async rootElement => { | ||||||
|  |   if (!ReactDOM.unmountComponentAtNode(rootElement)) | ||||||
|  |     throw new Error('Component was not mounted'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | window.playwrightUpdate = async (rootElement, component) => { | ||||||
|  |   ReactDOM.render(render(/** @type {Component} */(component)), rootElement); | ||||||
|  | }; | ||||||
| @ -9,19 +9,19 @@ | |||||||
|     "typecheck": "tsc --noEmit" |     "typecheck": "tsc --noEmit" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "react": "^17.0.2", |     "react": "^18.2.0", | ||||||
|     "react-dom": "^17.0.2", |     "react-dom": "^18.2.0", | ||||||
|     "react-router-dom": "^6.4.2" |     "react-router-dom": "^6.6.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/react": "^17.0.33", |     "@types/react": "^18.0.26", | ||||||
|     "@types/react-dom": "^17.0.10", |     "@types/react-dom": "^18.0.10", | ||||||
|     "@vitejs/plugin-react": "^3.0.0", |     "@vitejs/plugin-react": "^3.0.0", | ||||||
|     "typescript": "^4.5.4", |     "typescript": "^4.5.4", | ||||||
|     "vite": "^4.1.1" |     "vite": "^4.1.1" | ||||||
|   }, |   }, | ||||||
|   "@standaloneDevDependencies": { |   "@standaloneDevDependencies": { | ||||||
|     "@playwright/experimental-ct-react": "^1.22.2", |     "@playwright/experimental-ct-react": "^1.22.0", | ||||||
|     "@playwright/test": "^1.22.2" |     "@playwright/test": "^1.22.2" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { useRef } from "react" | import { useLayoutEffect, useRef, useState } from "react" | ||||||
| 
 | 
 | ||||||
|  type CounterProps = { |  type CounterProps = { | ||||||
|    count?: number; |    count?: number; | ||||||
| @ -9,11 +9,17 @@ import { useRef } from "react" | |||||||
|  let _remountCount = 1; |  let _remountCount = 1; | ||||||
| 
 | 
 | ||||||
|  export default function Counter(props: CounterProps) { |  export default function Counter(props: CounterProps) { | ||||||
|    const remountCount = useRef(_remountCount++); |    const [remountCount] = useState(_remountCount); | ||||||
|  |    const didMountRef = useRef(false) | ||||||
|  |    useLayoutEffect(() => { | ||||||
|  |      if (!didMountRef.current) { | ||||||
|  |        didMountRef.current = true; | ||||||
|  |        _remountCount++; | ||||||
|  |      } | ||||||
|  |    }, []) | ||||||
|    return <div onClick={() => props.onClick?.('hello')}> |    return <div onClick={() => props.onClick?.('hello')}> | ||||||
|      <div id="props">{ props.count }</div> |      <div id="props">{ props.count }</div> | ||||||
|      <div id="remount-count">{ remountCount.current }</div> |      <div id="remount-count">{ remountCount }</div> | ||||||
|      { props.children } |      { props.children } | ||||||
|    </div> |    </div> | ||||||
|  } |  } | ||||||
|   |  | ||||||
| @ -1,12 +1,13 @@ | |||||||
| import React from 'react'; | import * as React from 'react'; | ||||||
| import ReactDOM from 'react-dom'; | import { createRoot } from 'react-dom/client'; | ||||||
| import { BrowserRouter } from 'react-router-dom'; | import { BrowserRouter } from 'react-router-dom'; | ||||||
| import App from './App'; | import App from './App'; | ||||||
| import './assets/index.css'; | import './assets/index.css'; | ||||||
| 
 | 
 | ||||||
| ReactDOM.render( | createRoot(document.getElementById("root")!).render( | ||||||
|   <React.StrictMode> |   <React.StrictMode> | ||||||
|     <BrowserRouter><App /></BrowserRouter> |     <BrowserRouter> | ||||||
|   </React.StrictMode>, |       <App /> | ||||||
|   document.getElementById('root') |     </BrowserRouter> | ||||||
| ) |   </React.StrictMode> | ||||||
|  | ); | ||||||
|  | |||||||
| @ -3,19 +3,19 @@ | |||||||
|   "version": "0.1.0", |   "version": "0.1.0", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "react": "^17.0.2", |     "react": "^18.2.0", | ||||||
|     "react-dom": "^17.0.2", |     "react-dom": "^18.2.0", | ||||||
|     "react-router-dom": "^6.4.2"     |     "react-router-dom": "^6.6.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/node": "^16.11.26", |     "@types/node": "^16.11.26", | ||||||
|     "@types/react": "^17.0.39", |     "@types/react": "^18.0.26", | ||||||
|     "@types/react-dom": "^17.0.13", |     "@types/react-dom": "^18.0.10", | ||||||
|     "react-scripts": "5.0.0", |     "react-scripts": "5.0.0", | ||||||
|     "typescript": "^4.6.2" |     "typescript": "^4.6.2" | ||||||
|   }, |   }, | ||||||
|   "@standaloneDevDependencies": { |   "@standaloneDevDependencies": { | ||||||
|     "@playwright/experimental-ct-react": "^1.22.2", |     "@playwright/experimental-ct-react": "^1.2.2", | ||||||
|     "@playwright/test": "^1.22.2" |     "@playwright/test": "^1.22.2" | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { useRef } from "react" | import { useLayoutEffect, useRef, useState } from "react" | ||||||
| 
 | 
 | ||||||
|  type CounterProps = { |  type CounterProps = { | ||||||
|    count?: number; |    count?: number; | ||||||
| @ -9,11 +9,17 @@ import { useRef } from "react" | |||||||
|  let _remountCount = 1; |  let _remountCount = 1; | ||||||
| 
 | 
 | ||||||
|  export default function Counter(props: CounterProps) { |  export default function Counter(props: CounterProps) { | ||||||
|    const remountCount = useRef(_remountCount++); |    const [remountCount] = useState(_remountCount); | ||||||
|  |    const didMountRef = useRef(false) | ||||||
|  |    useLayoutEffect(() => { | ||||||
|  |      if (!didMountRef.current) { | ||||||
|  |        didMountRef.current = true; | ||||||
|  |        _remountCount++; | ||||||
|  |      } | ||||||
|  |    }, []) | ||||||
|    return <div onClick={() => props.onClick?.('hello')}> |    return <div onClick={() => props.onClick?.('hello')}> | ||||||
|      <div id="props">{ props.count }</div> |      <div id="props">{ props.count }</div> | ||||||
|      <div id="remount-count">{ remountCount.current }</div> |      <div id="remount-count">{ remountCount }</div> | ||||||
|      { props.children } |      { props.children } | ||||||
|    </div> |    </div> | ||||||
|  } |  } | ||||||
|   |  | ||||||
| @ -1,12 +1,13 @@ | |||||||
| import React from 'react'; | import * as React from 'react'; | ||||||
| import ReactDOM from 'react-dom'; | import { createRoot } from 'react-dom/client'; | ||||||
| import { BrowserRouter } from 'react-router-dom'; | import { BrowserRouter } from 'react-router-dom'; | ||||||
| import App from './App'; | import App from './App'; | ||||||
| import './assets/index.css'; | import './assets/index.css'; | ||||||
| 
 | 
 | ||||||
| ReactDOM.render( | createRoot(document.getElementById("root")).render( | ||||||
|   <React.StrictMode> |   <React.StrictMode> | ||||||
|     <BrowserRouter><App /></BrowserRouter> |     <BrowserRouter> | ||||||
|   </React.StrictMode>, |       <App /> | ||||||
|   document.getElementById('root') |     </BrowserRouter> | ||||||
|  |   </React.StrictMode> | ||||||
| ); | ); | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ test('should work with the empty component list', async ({ runInlineTest }, test | |||||||
|   expect(metainfo.version).toEqual(require('playwright-core/package.json').version); |   expect(metainfo.version).toEqual(require('playwright-core/package.json').version); | ||||||
|   expect(metainfo.viteVersion).toEqual(require('vite/package.json').version); |   expect(metainfo.viteVersion).toEqual(require('vite/package.json').version); | ||||||
|   expect(Object.entries(metainfo.tests)).toHaveLength(1); |   expect(Object.entries(metainfo.tests)).toHaveLength(1); | ||||||
|   expect(Object.entries(metainfo.sources)).toHaveLength(8); |   expect(Object.entries(metainfo.sources)).toHaveLength(9); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('should extract component list', async ({ runInlineTest }, testInfo) => { | test('should extract component list', async ({ runInlineTest }, testInfo) => { | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ | |||||||
|     "packages/*/lib", |     "packages/*/lib", | ||||||
|     "packages/html-reporter", |     "packages/html-reporter", | ||||||
|     "packages/playwright-ct-react", |     "packages/playwright-ct-react", | ||||||
|  |     "packages/playwright-ct-react17", | ||||||
|     "packages/playwright-ct-solid", |     "packages/playwright-ct-solid", | ||||||
|     "packages/playwright-ct-svelte", |     "packages/playwright-ct-svelte", | ||||||
|     "packages/playwright-ct-vue", |     "packages/playwright-ct-vue", | ||||||
|  | |||||||
| @ -178,6 +178,11 @@ const workspace = new Workspace(ROOT_PATH, [ | |||||||
|     path: path.join(ROOT_PATH, 'packages', 'playwright-ct-react'), |     path: path.join(ROOT_PATH, 'packages', 'playwright-ct-react'), | ||||||
|     files: ['LICENSE'], |     files: ['LICENSE'], | ||||||
|   }), |   }), | ||||||
|  |   new PWPackage({ | ||||||
|  |     name: '@playwright/experimental-ct-react17', | ||||||
|  |     path: path.join(ROOT_PATH, 'packages', 'playwright-ct-react17'), | ||||||
|  |     files: ['LICENSE'], | ||||||
|  |   }), | ||||||
|   new PWPackage({ |   new PWPackage({ | ||||||
|     name: '@playwright/experimental-ct-solid', |     name: '@playwright/experimental-ct-solid', | ||||||
|     path: path.join(ROOT_PATH, 'packages', 'playwright-ct-solid'), |     path: path.join(ROOT_PATH, 'packages', 'playwright-ct-solid'), | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sebastian Silbermann
						Sebastian Silbermann