Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ For full documentation, please see the [Playwright testing docs on the official
> This will resolve a build error related to `Symbol.asyncDispose`. If this is not added, tests may fail to run since the Stencil dev server will be unable
> to start due to the build error.

1. Ensure the Stencil project has a [`www` output target](https://stenciljs.com/docs/www). Playwright relies on pre-compiled output running in a dev server
1. Ensure the Stencil project has a [`www`](https://stenciljs.com/docs/www), `dist`, or `loader-bundle` output target. Playwright relies on pre-compiled output running in a dev server
to run tests against. When using the `createConfig()` helper, a configuration for the dev server will be automatically created based on
the Stencil project's `www` output target config and [dev server config](https://stenciljs.com/docs/dev-server). If no `www` output target is specified,
tests will not be able to run.
the Stencil project's output target config and [dev server config](https://stenciljs.com/docs/dev-server). The adapter will look for output targets in the following order of priority: `www` > `dist` (Stencil < v5) / `loader-bundle` (Stencil v5). If none of these output targets are specified,
tests may not be able to run.

1. Add the `copy` option to the `www` output target config:

Expand Down
47 changes: 37 additions & 10 deletions src/load-config-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@
import { loadConfig, OutputTargetWww } from '@stencil/core/compiler';
import { findUp } from 'find-up';
import { existsSync } from 'fs';
import { relative } from 'path';
import { join, relative } from 'path';

/**
* Common shape for output targets with dir and buildDir properties.
* Used for dist (v4) and loader-bundle (v5) targets.
*/
interface OutputTargetWithDir {
type: string;
dir?: string;
buildDir?: string;
}

const DEFAULT_NAMESPACE = 'app';
const DEFAULT_BASE_URL = 'http://localhost:3333';
Expand Down Expand Up @@ -33,24 +43,41 @@ export const loadConfigMeta = async (cwd?: string) => {
if (stencilConfigPath && existsSync(stencilConfigPath)) {
const { devServer, fsNamespace, outputTargets } = (await loadConfig({ configPath: stencilConfigPath })).config;

// Grab the WWW output target. If one doesn't exist, we'll throw a warning and roll
// with the default value for the entry path.
// Grab a suitable output target for script injection.
// Priority: www > dist (v4) > loader-bundle (v5)
const wwwTarget = outputTargets.find((o): o is OutputTargetWww => o.type === 'www');
const distTarget = outputTargets.find((o) => o.type === 'dist') as OutputTargetWithDir | undefined;
// loader-bundle is a v5 output target type, so we need to cast to avoid type errors on v4
const loaderBundleTarget = outputTargets.find((o) => (o as OutputTargetWithDir).type === 'loader-bundle') as
| OutputTargetWithDir
| undefined;

if (wwwTarget) {
// Get path from dev-server root to www
let relativePath = relative(devServer.root!, wwwTarget.dir!);
relativePath = relativePath === '' ? '.' : relativePath;
if (!relativePath.startsWith('.')) {
relativePath = `./${relativePath}`;
}
const relativePath = relative(devServer.root!, wwwTarget.dir!);

// Use buildDir from config (defaults to 'build' for www target)
const buildDir = (wwwTarget as unknown as { buildDir?: string }).buildDir ?? 'build';
const entryPath = join(relativePath, buildDir, fsNamespace);
stencilEntryPath = entryPath === '' ? '.' : entryPath.startsWith('.') ? entryPath : `./${entryPath}`;
} else if (distTarget || loaderBundleTarget) {
// Fall back to dist or loader-bundle target
const target = distTarget ?? loaderBundleTarget!;

// Get path from dev-server root to target dir
const relativePath = relative(devServer.root!, target.dir!);

stencilEntryPath = `${relativePath}/build/${fsNamespace}`;
// dist/loader-bundle use empty string as default buildDir
// Path structure: dir/buildDir/namespace/namespace (extra namespace folder)
const buildDir = target.buildDir ?? '';
const entryPath = join(relativePath, buildDir, fsNamespace, fsNamespace);
stencilEntryPath = entryPath === '' ? '.' : entryPath.startsWith('.') ? entryPath : `./${entryPath}`;
} else {
// Make a best guess at the entry path
stencilEntryPath = `${DEFAULT_STENCIL_ENTRY_PATH_PREFIX}/${fsNamespace}`;

console.warn(
`No "www" output target found in the Stencil config. Using default entry path: "${stencilEntryPath}". Tests using 'setContent' may fail to execute.`,
`No "www", "dist", or "loader-bundle" output target found in the Stencil config. Using default entry path: "${stencilEntryPath}". Tests using 'setContent' may fail to execute.`,
);
}

Expand Down
129 changes: 126 additions & 3 deletions src/test/load-config-meta.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ vi.mock('fs', () => ({
existsSync: () => existsSyncMock(),
}));

const stencilConfig = {
const stencilConfig: {
fsNamespace: string;
devServer: {
protocol: string;
address: string;
port: number;
pingRoute: string;
root: string;
};
outputTargets: Array<{ type: string; dir: string; buildDir?: string }>;
} = {
fsNamespace: 'mock-namespace',
devServer: {
protocol: 'http',
Expand Down Expand Up @@ -71,7 +81,7 @@ describe('loadConfigMeta', () => {
});
});

it('should log a warning if no "www" output target is found', async () => {
it('should log a warning if no supported output target is found', async () => {
stencilConfig.outputTargets = [];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');
Expand All @@ -80,7 +90,7 @@ describe('loadConfigMeta', () => {
const configMeta = await loadConfigMeta();

expect(consoleWarnSpy).toHaveBeenCalledWith(
`No "www" output target found in the Stencil config. Using default entry path: "./build/mock-namespace". Tests using 'setContent' may fail to execute.`,
`No "www", "dist", or "loader-bundle" output target found in the Stencil config. Using default entry path: "./build/mock-namespace". Tests using 'setContent' may fail to execute.`,
);
expect(configMeta).toEqual({
baseURL: 'http://localhost:4444',
Expand All @@ -90,6 +100,119 @@ describe('loadConfigMeta', () => {
});
});

it('should use www target with custom buildDir', async () => {
stencilConfig.outputTargets = [
{
type: 'www',
dir: '/mock-path/www',
buildDir: 'custom-build',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();

expect(configMeta).toEqual({
baseURL: 'http://localhost:4444',
stencilEntryPath: './www/custom-build/mock-namespace',
stencilNamespace: 'mock-namespace',
webServerUrl: 'http://localhost:4444/status',
});
});

it('should fall back to dist target when www is not available', async () => {
stencilConfig.outputTargets = [
{
type: 'dist',
dir: '/mock-path/dist',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();

expect(configMeta).toEqual({
baseURL: 'http://localhost:4444',
stencilEntryPath: './dist/mock-namespace/mock-namespace',
stencilNamespace: 'mock-namespace',
webServerUrl: 'http://localhost:4444/status',
});
});

it('should fall back to loader-bundle target when www and dist are not available', async () => {
stencilConfig.outputTargets = [
{
type: 'loader-bundle',
dir: '/mock-path/dist/loader-bundle',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();

expect(configMeta).toEqual({
baseURL: 'http://localhost:4444',
stencilEntryPath: './dist/loader-bundle/mock-namespace/mock-namespace',
stencilNamespace: 'mock-namespace',
webServerUrl: 'http://localhost:4444/status',
});
});

it('should prefer www target over dist and loader-bundle', async () => {
stencilConfig.outputTargets = [
{
type: 'dist',
dir: '/mock-path/dist',
},
{
type: 'www',
dir: '/mock-path/www',
},
{
type: 'loader-bundle',
dir: '/mock-path/dist/loader-bundle',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();

expect(configMeta).toEqual({
baseURL: 'http://localhost:4444',
stencilEntryPath: './www/build/mock-namespace',
stencilNamespace: 'mock-namespace',
webServerUrl: 'http://localhost:4444/status',
});
});

it('should prefer dist target over loader-bundle', async () => {
stencilConfig.outputTargets = [
{
type: 'loader-bundle',
dir: '/mock-path/dist/loader-bundle',
},
{
type: 'dist',
dir: '/mock-path/dist',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();

expect(configMeta).toEqual({
baseURL: 'http://localhost:4444',
stencilEntryPath: './dist/mock-namespace/mock-namespace',
stencilNamespace: 'mock-namespace',
webServerUrl: 'http://localhost:4444/status',
});
});

it('should log a warning if no Stencil config path was found', async () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

Expand Down
Loading