diff --git a/packages/playwright-ct-core/src/tsxTransform.ts b/packages/playwright-ct-core/src/tsxTransform.ts index a0f35bc4a6..6ef051a6b4 100644 --- a/packages/playwright-ct-core/src/tsxTransform.ts +++ b/packages/playwright-ct-core/src/tsxTransform.ts @@ -17,7 +17,8 @@ import path from 'path'; import type { T, BabelAPI, PluginObj } from 'playwright/src/transform/babelBundle'; import { types, declare, traverse } from 'playwright/lib/transform/babelBundle'; -import { resolveHook, setTransformData } from 'playwright/lib/transform/transform'; +import { resolveImportSpecifierExtension } from 'playwright/lib/util'; +import { setTransformData } from 'playwright/lib/transform/transform'; const t: typeof T = types; let jsxComponentNames: Set; @@ -71,15 +72,15 @@ export default declare((api: BabelAPI) => { const importNode = p.node; if (!t.isStringLiteral(importNode.source)) return; - const importPath = resolveImportSource(importNode.source.value, this.filename!); - const ext = path.extname(importPath); + + const ext = path.extname(importNode.source.value); // Convert all non-JS imports into refs. if (!allJsExtensions.has(ext)) { for (const specifier of importNode.specifiers) { if (t.isImportNamespaceSpecifier(specifier)) continue; - const { localName, info } = importInfo(importPath, specifier, this.filename!); + const { localName, info } = importInfo(importNode, specifier, this.filename!); importInfos.set(localName, info); } p.skip(); @@ -92,7 +93,7 @@ export default declare((api: BabelAPI) => { for (const specifier of importNode.specifiers) { if (t.isImportNamespaceSpecifier(specifier)) continue; - const { localName, info } = importInfo(importPath, specifier, this.filename!); + const { localName, info } = importInfo(importNode, specifier, this.filename!); if (jsxComponentNames.has(localName)) { importInfos.set(localName, info); ++importCount; @@ -143,22 +144,25 @@ function collectJsxComponentUsages(node: T.Node): Set { export type ImportInfo = { id: string; + isModuleOrAlias: boolean; importPath: string; remoteName: string | undefined; }; -function resolveImportSource(importSource: string, filename: string): string { +export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportSpecifier | T.ImportDefaultSpecifier, filename: string): { localName: string, info: ImportInfo } { + const importSource = importNode.source.value; + const isModuleOrAlias = !importSource.startsWith('.'); const unresolvedImportPath = path.resolve(path.dirname(filename), importSource); - const importPath = resolveHook(filename, importSource) || unresolvedImportPath; - return importPath; -} - -function importInfo(importPath: string, specifier: T.ImportSpecifier | T.ImportDefaultSpecifier, filename: string): { localName: string, info: ImportInfo } { + // Support following notations for Button.tsx: + // - import { Button } from './Button.js' - via resolveImportSpecifierExtension + // - import { Button } from './Button' - via require.resolve + const importPath = isModuleOrAlias ? importSource : resolveImportSpecifierExtension(unresolvedImportPath) || require.resolve(unresolvedImportPath); const idPrefix = importPath.replace(/[^\w_\d]/g, '_'); const result: ImportInfo = { id: idPrefix, importPath, + isModuleOrAlias, remoteName: undefined, }; diff --git a/packages/playwright-ct-core/src/viteUtils.ts b/packages/playwright-ct-core/src/viteUtils.ts index 752f75ef32..249537b4a7 100644 --- a/packages/playwright-ct-core/src/viteUtils.ts +++ b/packages/playwright-ct-core/src/viteUtils.ts @@ -143,7 +143,7 @@ export async function populateComponentsFromTests(componentRegistry: ComponentRe for (const importInfo of importList) componentRegistry.set(importInfo.id, importInfo); if (componentsByImportingFile) - componentsByImportingFile.set(file, importList.map(i => i.importPath)); + componentsByImportingFile.set(file, importList.filter(i => !i.isModuleOrAlias).map(i => i.importPath)); } } @@ -179,7 +179,7 @@ export function transformIndexFile(id: string, content: string, templateDir: str lines.push(registerSource); for (const value of importInfos.values()) { - const importPath = './' + path.relative(folder, value.importPath).replace(/\\/g, '/'); + const importPath = value.isModuleOrAlias ? value.importPath : './' + path.relative(folder, value.importPath).replace(/\\/g, '/'); lines.push(`const ${value.id} = () => import('${importPath}').then((mod) => mod.${value.remoteName || 'default'});`); } diff --git a/tests/playwright-test/playwright.ct-build.spec.ts b/tests/playwright-test/playwright.ct-build.spec.ts index efc75878bd..6304b44a12 100644 --- a/tests/playwright-test/playwright.ct-build.spec.ts +++ b/tests/playwright-test/playwright.ct-build.spec.ts @@ -143,25 +143,31 @@ test('should extract component list', async ({ runInlineTest }, testInfo) => { id: expect.stringContaining('playwright_test_src_button_tsx_Button'), remoteName: 'Button', importPath: expect.stringContaining('button.tsx'), + isModuleOrAlias: false, }, { id: expect.stringContaining('playwright_test_src_clashingNames1_tsx_ClashingName'), remoteName: 'ClashingName', importPath: expect.stringContaining('clashingNames1.tsx'), + isModuleOrAlias: false, }, { id: expect.stringContaining('playwright_test_src_clashingNames2_tsx_ClashingName'), remoteName: 'ClashingName', importPath: expect.stringContaining('clashingNames2.tsx'), + isModuleOrAlias: false, }, { id: expect.stringContaining('playwright_test_src_components_tsx_Component1'), remoteName: 'Component1', importPath: expect.stringContaining('components.tsx'), + isModuleOrAlias: false, }, { id: expect.stringContaining('playwright_test_src_components_tsx_Component2'), remoteName: 'Component2', importPath: expect.stringContaining('components.tsx'), + isModuleOrAlias: false, }, { id: expect.stringContaining('playwright_test_src_defaultExport_tsx'), importPath: expect.stringContaining('defaultExport.tsx'), + isModuleOrAlias: false, }]); for (const [, value] of Object.entries(metainfo.deps)) @@ -458,6 +464,7 @@ test('should retain deps when test changes', async ({ runInlineTest }, testInfo) id: expect.stringContaining('playwright_test_src_button_tsx_Button'), remoteName: 'Button', importPath: expect.stringContaining('button.tsx'), + isModuleOrAlias: false, }]); for (const [, value] of Object.entries(metainfo.deps)) @@ -550,31 +557,6 @@ test('should pass imported images from test to component', async ({ runInlineTes expect(result.passed).toBe(1); }); -test('should import from file shortcuts (no .ts ext)', async ({ runInlineTest }, testInfo) => { - const result = await runInlineTest({ - 'playwright.config.ts': playwrightConfig, - 'playwright/index.html': ``, - 'playwright/index.ts': ``, - 'src/tab.types.ts': ` - export enum ALLOWED_TABS { - DRAW = "DRAW", - TYPE = "TYPE", - IMAGE = "IMAGE", - } - `, - 'src/image.test.tsx': ` - import { test, expect } from '@playwright/experimental-ct-react'; - import { ALLOWED_TABS } from './tab.types'; - test('pass', async ({ mount }) => { - expect(ALLOWED_TABS.DRAW).toBe('DRAW'); - }); - `, - }, { workers: 1 }); - - expect(result.exitCode).toBe(0); - expect(result.passed).toBe(1); -}); - test('should pass dates, regex, urls and bigints', async ({ runInlineTest }) => { const result = await runInlineTest({ 'playwright.config.ts': playwrightConfig, diff --git a/tests/playwright-test/playwright.ct-resolve.spec.ts b/tests/playwright-test/playwright.ct-resolve.spec.ts deleted file mode 100644 index bebcd93fc1..0000000000 --- a/tests/playwright-test/playwright.ct-resolve.spec.ts +++ /dev/null @@ -1,59 +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 { test, expect } from './playwright-test-fixtures'; - -test.describe.configure({ mode: 'parallel' }); - -const playwrightConfig = ` -import { defineConfig } from '@playwright/experimental-ct-react'; -import path from 'path'; -export default defineConfig({ - use: { - ctPort: ${3200 + (+process.env.TEST_PARALLEL_INDEX)}, - }, - projects: [{name: 'foo'}], -}); -`; - -test('should resolve component names using tsconfig', async ({ runInlineTest }, testInfo) => { - const result = await runInlineTest({ - 'playwright.config.ts': playwrightConfig, - 'playwright/index.html': ``, - 'playwright/index.js': ``, - 'tsconfig.json': `{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@test/*": ["./src/*"], - }, - }, - }`, - 'src/button.tsx': ` - export const Button = () => ; - `, - 'tests/button.spec.tsx': ` - import { test, expect } from '@playwright/experimental-ct-react'; - import { Button } from '@test/button'; - test('pass', async ({ mount }) => { - const component = await mount(); - await expect(component).toHaveText('Button'); - }); - `, - }, { workers: 1 }); - expect(result.exitCode).toBe(0); - expect(result.passed).toBe(1); -});