diff --git a/packages/playwright-test/src/experimentalLoader.ts b/packages/playwright-test/src/experimentalLoader.ts index 3a033e05c8..c3fe0aa88a 100644 --- a/packages/playwright-test/src/experimentalLoader.ts +++ b/packages/playwright-test/src/experimentalLoader.ts @@ -16,7 +16,7 @@ import fs from 'fs'; import url from 'url'; -import { transformHook, resolveHook } from './transform'; +import { transformHook, resolveHook, belongsToNodeModules } from './transform'; async function resolve(specifier: string, context: { parentURL: string }, defaultResolve: any) { if (context.parentURL && context.parentURL.startsWith('file://')) { @@ -29,13 +29,27 @@ async function resolve(specifier: string, context: { parentURL: string }, defaul } async function load(moduleUrl: string, context: any, defaultLoad: any) { - if (moduleUrl.startsWith('file://') && (moduleUrl.endsWith('.ts') || moduleUrl.endsWith('.tsx'))) { - const filename = url.fileURLToPath(moduleUrl); - const code = fs.readFileSync(filename, 'utf-8'); - const source = transformHook(code, filename, moduleUrl); - return { format: 'module', source }; - } - return defaultLoad(moduleUrl, context, defaultLoad); + // Bail out for wasm, json, etc. + // non-js files have context.format === undefined + if (context.format !== 'commonjs' && context.format !== 'module' && context.format !== undefined) + return defaultLoad(moduleUrl, context, defaultLoad); + + // Bail for built-in modules. + if (!moduleUrl.startsWith('file://')) + return defaultLoad(moduleUrl, context, defaultLoad); + + if (!moduleUrl.startsWith('file://')) + return defaultLoad(moduleUrl, context, defaultLoad); + + const filename = url.fileURLToPath(moduleUrl); + // Bail for node_modules. + if (belongsToNodeModules(filename)) + return defaultLoad(moduleUrl, context, defaultLoad); + + const code = fs.readFileSync(filename, 'utf-8'); + const source = transformHook(code, filename, moduleUrl); + // Output format is always the same as input format, if it was unknown, we always report modules. + return { format: context.format || 'module', source }; } module.exports = { resolve, load }; diff --git a/packages/playwright-test/src/transform.ts b/packages/playwright-test/src/transform.ts index 85b17ae16b..0d67f5c1d9 100644 --- a/packages/playwright-test/src/transform.ts +++ b/packages/playwright-test/src/transform.ts @@ -135,7 +135,7 @@ export function resolveHook(filename: string, specifier: string): string | undef if (value.includes('*')) candidate = candidate.replace('*', matchedPartOfSpecifier); candidate = path.resolve(tsconfig.absoluteBaseUrl, candidate.replace(/\//g, path.sep)); - for (const ext of ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx']) { + for (const ext of ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.cjs', '.mts', '.cts']) { if (fs.existsSync(candidate + ext)) { if (keyPrefix.length > longestPrefixLength) { longestPrefixLength = keyPrefix.length; @@ -162,15 +162,11 @@ export function transformHook(code: string, filename: string, moduleUrl?: string // If we are not TypeScript and there is no applicable preprocessor - bail out. const isModule = !!moduleUrl; const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx'); - const isJSX = filename.endsWith('.jsx'); const hasPreprocessor = process.env.PW_TEST_SOURCE_TRANSFORM && process.env.PW_TEST_SOURCE_TRANSFORM_SCOPE && process.env.PW_TEST_SOURCE_TRANSFORM_SCOPE.split(pathSeparator).some(f => filename.startsWith(f)); - if (!isTypeScript && !isJSX && !hasPreprocessor) - return code; - const cachePath = calculateCachePath(code, filename, isModule); const codePath = cachePath + '.js'; const sourceMapPath = cachePath + '.map'; @@ -212,11 +208,11 @@ export function installTransform(): () => void { } (Module as any)._resolveFilename = resolveFilename; - const exts = ['.ts', '.tsx', '.jsx']; - // When script preprocessor is engaged, we transpile JS as well. - if (scriptPreprocessor) - exts.push('.js', '.mjs'); - const revertPirates = pirates.addHook((code: string, filename: string) => transformHook(code, filename), { exts }); + const revertPirates = pirates.addHook((code: string, filename: string) => { + if (belongsToNodeModules(filename)) + return code; + return transformHook(code, filename); + }, { exts: ['.ts', '.tsx', '.js', '.jsx', '.mjs'] }); return () => { reverted = true; @@ -248,3 +244,13 @@ export function wrapFunctionWithLocation(func: (location: Lo return func(location, ...args); }; } + +export function belongsToNodeModules(file: string) { + if (file.includes(`${path.sep}node_modules${path.sep}`)) + return true; + if (file.includes(`${path.sep}playwright${path.sep}packages${path.sep}playwright`)) + return true; + if (file.includes(`${path.sep}playwright${path.sep}tests${path.sep}config${path.sep}coverage.js`)) + return true; + return false; +} diff --git a/tests/components/ct-vue-vite/src/notation-vue.spec.js b/tests/components/ct-vue-vite/src/notation-vue.spec.js new file mode 100644 index 0000000000..1bfe754604 --- /dev/null +++ b/tests/components/ct-vue-vite/src/notation-vue.spec.js @@ -0,0 +1,81 @@ +import { test, expect } from '@playwright/experimental-ct-vue' + +import has from 'has' +import Button from './components/Button.vue' +import DefaultSlot from './components/DefaultSlot.vue' +import NamedSlots from './components/NamedSlots.vue' +import Component from './components/Component.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') +}) + +test('optionless should work', async ({ mount }) => { + const component = await mount(Component) + await expect(component).toContainText('test') +}) + +test('should run hooks', async ({ page, mount }) => { + const messages = [] + page.on('console', m => messages.push(m.text())) + await mount(Button, { + props: { + title: 'Submit' + }, + hooksConfig: { route: 'A' } + }) + expect(messages).toEqual(['Before mount: {\"route\":\"A\"}, app: true', 'After mount el: HTMLButtonElement']) +}) diff --git a/tests/library/component-parser.spec.ts b/tests/library/component-parser.spec.ts index 82f91e91da..86ded3acee 100644 --- a/tests/library/component-parser.spec.ts +++ b/tests/library/component-parser.spec.ts @@ -16,7 +16,7 @@ import { playwrightTest as it, expect } from '../config/browserTest'; import type { AttributeSelector } from '../../packages/playwright-core/src/server/isomorphic/selectorParser'; -import { parseAttributeSelector } from '../../packages/playwright-core/src/server/isomorphic/selectorParser'; +import { parseAttributeSelector } from '../../packages/playwright-core/lib/server/isomorphic/selectorParser'; const parse = (selector: string) => parseAttributeSelector(selector, false); const serialize = (parsed: AttributeSelector) => { diff --git a/tests/playwright-test/loader.spec.ts b/tests/playwright-test/loader.spec.ts index fbbdf86124..e01aae4567 100644 --- a/tests/playwright-test/loader.spec.ts +++ b/tests/playwright-test/loader.spec.ts @@ -28,7 +28,8 @@ test('should return the location of a syntax error', async ({ runInlineTest }) = expect(result.exitCode).toBe(1); expect(result.passed).toBe(0); expect(result.failed).toBe(0); - expect(result.output).toContain('error.spec.js:6'); + expect(result.output).toContain('error.spec.js'); + expect(result.output).toContain('(6:18)'); }); test('should print an improper error', async ({ runInlineTest }) => { @@ -163,19 +164,17 @@ test('should load an mjs file', async ({ runInlineTest }) => { expect(exitCode).toBe(0); }); -test('should throw a nice error if a js file uses import', async ({ runInlineTest }) => { - const { exitCode, output } = await runInlineTest({ +test('should allow using import', async ({ runInlineTest }) => { + const { exitCode } = await runInlineTest({ 'a.spec.js': ` import fs from 'fs'; - const { test } = folio; + const { test } = pwt; test('succeeds', () => { expect(1 + 1).toBe(2); }); ` }); - expect(exitCode).toBe(1); - expect(output).toContain('a.spec.js'); - expect(output).toContain('Cannot use import statement outside a module'); + expect(exitCode).toBe(0); }); test('should load esm when package.json has type module', async ({ runInlineTest }) => { diff --git a/tests/playwright-test/reporter-base.spec.ts b/tests/playwright-test/reporter-base.spec.ts index 7955d259ff..7322fe3a37 100644 --- a/tests/playwright-test/reporter-base.spec.ts +++ b/tests/playwright-test/reporter-base.spec.ts @@ -261,8 +261,9 @@ test('should print errors with inconsistent message/stack', async ({ runInlineTe }); expect(result.exitCode).toBe(1); expect(result.failed).toBe(1); - expect(result.output).toContain('hi!Error: Hello'); - expect(result.output).toContain('at myTest'); + const output = stripAnsi(result.output); + expect(output).toContain('hi!Error: Hello'); + expect(output).toContain('function myTest'); }); test('should print "no tests found" error', async ({ runInlineTest }) => {