creevey 0.10.0-beta.31 → 0.10.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/shared/components/ResultsPage.d.ts +1 -1
- package/dist/client/web/CreeveyApp.js +1 -0
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/server/docker.d.ts +1 -1
- package/dist/server/docker.js +21 -4
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +9 -10
- package/dist/server/index.js.map +1 -1
- package/dist/server/master/start.js +1 -1
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/docker-file.d.ts +1 -2
- package/dist/server/playwright/docker-file.js +10 -4
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/docker.d.ts +2 -1
- package/dist/server/playwright/docker.js +10 -2
- package/dist/server/playwright/docker.js.map +1 -1
- package/dist/server/playwright/internal.d.ts +3 -4
- package/dist/server/playwright/internal.js +48 -37
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.js +1 -7
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/selenium/internal.js +5 -12
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/webdriver.js +0 -7
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/telemetry.js +2 -2
- package/dist/server/utils.d.ts +1 -2
- package/dist/server/utils.js +11 -8
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +2 -0
- package/dist/server/webdriver.js +13 -1
- package/dist/server/webdriver.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client/web/CreeveyApp.tsx +1 -0
- package/src/server/docker.ts +24 -4
- package/src/server/index.ts +11 -14
- package/src/server/master/start.ts +1 -1
- package/src/server/playwright/docker-file.ts +12 -5
- package/src/server/playwright/docker.ts +16 -3
- package/src/server/playwright/index-source.mjs +16 -0
- package/src/server/playwright/internal.ts +78 -52
- package/src/server/playwright/webdriver.ts +1 -7
- package/src/server/selenium/internal.ts +5 -12
- package/src/server/selenium/webdriver.ts +0 -7
- package/src/server/telemetry.ts +2 -2
- package/src/server/utils.ts +33 -25
- package/src/server/webdriver.ts +13 -0
- package/src/types.ts +0 -2
package/src/server/docker.ts
CHANGED
@@ -5,6 +5,7 @@ import Dockerode, { Container } from 'dockerode';
|
|
5
5
|
import { DockerAuth } from '../types.js';
|
6
6
|
import { subscribeOn } from './messages.js';
|
7
7
|
import { logger } from './logger.js';
|
8
|
+
import { waitForBrowserClose } from './webdriver.js';
|
8
9
|
|
9
10
|
const docker = new Dockerode();
|
10
11
|
|
@@ -58,12 +59,13 @@ export async function pullImages(
|
|
58
59
|
}
|
59
60
|
}
|
60
61
|
|
61
|
-
export async function buildImage(imageName: string, dockerfile: string): Promise<void> {
|
62
|
+
export async function buildImage(imageName: string, version: string, dockerfile: string): Promise<void> {
|
62
63
|
const images = await docker.listImages({ filters: { label: [`creevey=${imageName}`] } });
|
63
64
|
|
64
|
-
|
65
|
+
const containers = await docker.listContainers({ all: true, filters: { label: [`creevey=${imageName}`] } });
|
66
|
+
if (containers.length > 0) {
|
65
67
|
await Promise.all(
|
66
|
-
|
68
|
+
containers.map(async (info) => {
|
67
69
|
const container = docker.getContainer(info.Id);
|
68
70
|
try {
|
69
71
|
await container.remove({ force: true });
|
@@ -72,6 +74,23 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
72
74
|
}
|
73
75
|
}),
|
74
76
|
);
|
77
|
+
}
|
78
|
+
|
79
|
+
const oldImages = images.filter((info) => info.Labels.version !== version);
|
80
|
+
if (oldImages.length > 0) {
|
81
|
+
await Promise.all(
|
82
|
+
oldImages.map(async (info) => {
|
83
|
+
const image = docker.getImage(info.Id);
|
84
|
+
try {
|
85
|
+
await image.remove({ force: true });
|
86
|
+
} catch {
|
87
|
+
/* noop */
|
88
|
+
}
|
89
|
+
}),
|
90
|
+
);
|
91
|
+
}
|
92
|
+
|
93
|
+
if (oldImages.length !== images.length) {
|
75
94
|
logger().info(`Image ${imageName} already exists`);
|
76
95
|
return;
|
77
96
|
}
|
@@ -91,7 +110,7 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
91
110
|
// @ts-expect-error Type incompatibility AsyncIterator and AsyncIterableIterator
|
92
111
|
pack,
|
93
112
|
// TODO Support buildkit decode grpc (version: '2')
|
94
|
-
{ t: imageName, labels: { creevey: imageName }, version: '1' },
|
113
|
+
{ t: imageName, labels: { creevey: imageName, version }, version: '1' },
|
95
114
|
(buildError: Error | null, stream) => {
|
96
115
|
if (buildError || !stream) {
|
97
116
|
// spinner.error(buildError?.message);
|
@@ -151,6 +170,7 @@ export async function runImage(
|
|
151
170
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
152
171
|
subscribeOn('shutdown', async () => {
|
153
172
|
try {
|
173
|
+
await Promise.race([waitForBrowserClose(), new Promise((resolve) => setTimeout(resolve, 2_000))]);
|
154
174
|
await container.remove({ force: true });
|
155
175
|
} catch {
|
156
176
|
/* noop */
|
package/src/server/index.ts
CHANGED
@@ -6,13 +6,13 @@ import { resolveCommand } from 'package-manager-detector/commands';
|
|
6
6
|
import { readConfig, defaultBrowser } from './config.js';
|
7
7
|
import { Options, Config, BrowserConfigObject, isWorkerMessage } from '../types.js';
|
8
8
|
import { logger } from './logger.js';
|
9
|
+
import { getStorybookUrl, checkIsStorybookConnected } from './connection.js';
|
9
10
|
import { SeleniumWebdriver } from './selenium/webdriver.js';
|
10
11
|
import { LOCALHOST_REGEXP } from './webdriver.js';
|
11
12
|
import { isInsideDocker, killTree, resolvePlaywrightBrowserType, shutdownWithError } from './utils.js';
|
12
13
|
import { sendWorkerMessage, subscribeOn } from './messages.js';
|
13
14
|
import { buildImage } from './docker.js';
|
14
15
|
import { mkdir, writeFile } from 'fs/promises';
|
15
|
-
import { getStorybookUrl, checkIsStorybookConnected } from './connection.js';
|
16
16
|
import assert from 'assert';
|
17
17
|
|
18
18
|
async function startWebdriverServer(browser: string, config: Config, options: Options): Promise<string | undefined> {
|
@@ -35,7 +35,7 @@ async function startWebdriverServer(browser: string, config: Config, options: Op
|
|
35
35
|
if (cluster.isPrimary) return undefined;
|
36
36
|
|
37
37
|
const { browserName } = config.browsers[browser] as BrowserConfigObject;
|
38
|
-
return `creevey://${resolvePlaywrightBrowserType(browserName)}
|
38
|
+
return `creevey://${resolvePlaywrightBrowserType(browserName)}`;
|
39
39
|
}
|
40
40
|
|
41
41
|
const {
|
@@ -48,24 +48,21 @@ async function startWebdriverServer(browser: string, config: Config, options: Op
|
|
48
48
|
const { browserName } = config.browsers[browser] as BrowserConfigObject;
|
49
49
|
|
50
50
|
const imageName = `creevey/${browserName}:v${version}`;
|
51
|
-
const host = await startPlaywrightContainer(imageName, options.debug);
|
51
|
+
const host = await startPlaywrightContainer(imageName, browser, config, options.debug);
|
52
52
|
|
53
53
|
return host;
|
54
54
|
} else {
|
55
55
|
const { playwrightDockerFile } = await import('./playwright/docker-file.js');
|
56
|
-
const
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
),
|
61
|
-
),
|
62
|
-
];
|
56
|
+
const {
|
57
|
+
default: { version: creeveyVersion },
|
58
|
+
} = await import('../../package.json', { with: { type: 'json' } });
|
59
|
+
const browsers = [...new Set(Object.values(config.browsers).map((c) => (c as BrowserConfigObject).browserName))];
|
63
60
|
await Promise.all(
|
64
|
-
browsers.map(async (
|
61
|
+
browsers.map(async (browserName) => {
|
65
62
|
const imageName = `creevey/${browserName}:v${version}`;
|
66
|
-
const dockerfile = playwrightDockerFile(browserName, version
|
63
|
+
const dockerfile = await playwrightDockerFile(browserName, version);
|
67
64
|
|
68
|
-
await buildImage(imageName, dockerfile);
|
65
|
+
await buildImage(imageName, creeveyVersion, dockerfile);
|
69
66
|
}),
|
70
67
|
);
|
71
68
|
|
@@ -107,7 +104,7 @@ export default async function (options: Options): Promise<void> {
|
|
107
104
|
gridUrl = await startWebdriverServer(browser, config, options);
|
108
105
|
}
|
109
106
|
|
110
|
-
if (cluster.isPrimary) {
|
107
|
+
if (cluster.isPrimary && process.env.CI !== 'true') {
|
111
108
|
const [localUrl, remoteUrl] = getStorybookUrl(config, options);
|
112
109
|
const pm = getUserAgent();
|
113
110
|
assert(pm, new Error('Failed to detect current package manager'));
|
@@ -109,7 +109,7 @@ export async function start(
|
|
109
109
|
})
|
110
110
|
.finally(() => {
|
111
111
|
// NOTE: Take some time to kill processes
|
112
|
-
void shutdownWorkers().then(() => setTimeout(() => process.exit(),
|
112
|
+
void shutdownWorkers().then(() => setTimeout(() => process.exit(), 1_000));
|
113
113
|
});
|
114
114
|
});
|
115
115
|
// TODO grep
|
@@ -1,10 +1,12 @@
|
|
1
|
+
import { readFile } from 'fs/promises';
|
2
|
+
import { pathToFileURL } from 'url';
|
1
3
|
import semver from 'semver';
|
2
4
|
import { exec } from 'shelljs';
|
3
|
-
|
4
|
-
|
5
|
+
|
6
|
+
const importMetaUrl = pathToFileURL(__filename).href;
|
5
7
|
|
6
8
|
// TODO Support custom docker images
|
7
|
-
export function playwrightDockerFile(browser: string, version: string
|
9
|
+
export async function playwrightDockerFile(browser: string, version: string): Promise<string> {
|
8
10
|
const sv = semver.coerce(version);
|
9
11
|
|
10
12
|
let npmRegistry;
|
@@ -14,14 +16,19 @@ export function playwrightDockerFile(browser: string, version: string, serverOpt
|
|
14
16
|
/* noop */
|
15
17
|
}
|
16
18
|
|
19
|
+
const indexJs = await readFile(new URL('./index-source.mjs', importMetaUrl), 'utf-8');
|
20
|
+
|
17
21
|
return `
|
18
22
|
FROM node:lts
|
19
23
|
|
20
24
|
WORKDIR /creevey
|
21
25
|
|
22
26
|
RUN echo "{ \\"type\\": \\"module\\" }" > package.json && \\
|
23
|
-
|
24
|
-
|
27
|
+
${indexJs
|
28
|
+
.split('\n')
|
29
|
+
.map((line) => `echo "${line.replace(/"/g, '\\"')}" >> index.js && \\`)
|
30
|
+
.join('\n')}
|
31
|
+
${
|
25
32
|
npmRegistry
|
26
33
|
? `
|
27
34
|
echo "registry=${npmRegistry}" > .npmrc && \\`
|
@@ -1,9 +1,17 @@
|
|
1
|
+
import assert from 'assert';
|
1
2
|
import { runImage } from '../docker';
|
2
3
|
import { emitWorkerMessage, subscribeOn } from '../messages';
|
3
|
-
import { isInsideDocker } from '../utils';
|
4
|
+
import { getCreeveyCache, isInsideDocker, resolvePlaywrightBrowserType } from '../utils';
|
4
5
|
import { LOCALHOST_REGEXP } from '../webdriver';
|
6
|
+
import type { BrowserConfigObject, Config } from '../../types';
|
5
7
|
|
6
|
-
export async function startPlaywrightContainer(
|
8
|
+
export async function startPlaywrightContainer(
|
9
|
+
imageName: string,
|
10
|
+
browser: string,
|
11
|
+
config: Config,
|
12
|
+
debug: boolean,
|
13
|
+
): Promise<string> {
|
14
|
+
const { browserName, playwrightOptions } = config.browsers[browser] as BrowserConfigObject;
|
7
15
|
const port = await new Promise<number>((resolve) => {
|
8
16
|
subscribeOn('worker', (message) => {
|
9
17
|
if (message.type == 'port') {
|
@@ -13,13 +21,18 @@ export async function startPlaywrightContainer(imageName: string, debug: boolean
|
|
13
21
|
emitWorkerMessage({ type: 'port', payload: { port: -1 } });
|
14
22
|
});
|
15
23
|
|
24
|
+
const cacheDir = await getCreeveyCache();
|
25
|
+
|
26
|
+
assert(cacheDir, "Couldn't get cache directory");
|
27
|
+
|
16
28
|
const host = await runImage(
|
17
29
|
imageName,
|
18
|
-
[],
|
30
|
+
[JSON.stringify({ ...playwrightOptions, browser: resolvePlaywrightBrowserType(browserName) })],
|
19
31
|
{
|
20
32
|
ExposedPorts: { [`${port}/tcp`]: {} },
|
21
33
|
HostConfig: {
|
22
34
|
PortBindings: { ['4444/tcp']: [{ HostPort: `${port}` }] },
|
35
|
+
Binds: [`${cacheDir}/${process.pid}:/creevey/traces`],
|
23
36
|
},
|
24
37
|
},
|
25
38
|
debug,
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { chromium, firefox, webkit } from 'playwright-core';
|
2
|
+
|
3
|
+
/** @type import("playwright-core").LaunchOptions & { browser: 'chromium' | 'firefox' | 'webkit' } */
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
5
|
+
const config = JSON.parse(process.argv.slice(2)[0]);
|
6
|
+
|
7
|
+
const browsers = { chromium, firefox, webkit };
|
8
|
+
|
9
|
+
const ws = await browsers[config.browser].launchServer({
|
10
|
+
...config,
|
11
|
+
port: 4444,
|
12
|
+
wsPath: 'creevey',
|
13
|
+
tracesDir: 'traces',
|
14
|
+
});
|
15
|
+
|
16
|
+
console.log(config.browser, 'browser server launched on:', ws.wsEndpoint());
|
@@ -1,8 +1,10 @@
|
|
1
|
-
import
|
1
|
+
import path from 'path';
|
2
|
+
import { Browser, BrowserContext, BrowserType, Page, chromium, firefox, webkit } from 'playwright-core';
|
2
3
|
import chalk from 'chalk';
|
3
4
|
import { v4 } from 'uuid';
|
4
5
|
import Logger from 'loglevel';
|
5
6
|
import prefix from 'loglevel-plugin-prefix';
|
7
|
+
import type { Args } from '@storybook/csf';
|
6
8
|
import {
|
7
9
|
BrowserConfigObject,
|
8
10
|
Config,
|
@@ -11,13 +13,18 @@ import {
|
|
11
13
|
StoryInput,
|
12
14
|
StorybookEvents,
|
13
15
|
StorybookGlobals,
|
14
|
-
noop,
|
15
16
|
} from '../../types';
|
16
|
-
import {
|
17
|
-
|
18
|
-
|
17
|
+
import {
|
18
|
+
appendIframePath,
|
19
|
+
getAddresses,
|
20
|
+
LOCALHOST_REGEXP,
|
21
|
+
openBrowser,
|
22
|
+
resolveStorybookUrl,
|
23
|
+
storybookRootID,
|
24
|
+
} from '../webdriver';
|
25
|
+
import { getCreeveyCache, isShuttingDown, resolvePlaywrightBrowserType, runSequence } from '../utils';
|
19
26
|
import { colors, logger } from '../logger';
|
20
|
-
import
|
27
|
+
import assert from 'assert';
|
21
28
|
|
22
29
|
const browsers = {
|
23
30
|
chromium,
|
@@ -59,22 +66,34 @@ async function tryConnect(type: BrowserType, gridUrl: string): Promise<Browser |
|
|
59
66
|
export class InternalBrowser {
|
60
67
|
#isShuttingDown = false;
|
61
68
|
#browser: Browser;
|
69
|
+
#context: BrowserContext;
|
62
70
|
#page: Page;
|
71
|
+
#traceDir: string;
|
63
72
|
#sessionId: string = v4();
|
64
73
|
#serverHost: string | null = null;
|
65
74
|
#serverPort: number;
|
75
|
+
#debug: boolean;
|
66
76
|
#storybookGlobals?: StorybookGlobals;
|
67
|
-
#
|
68
|
-
constructor(
|
77
|
+
#closeBrowser = openBrowser();
|
78
|
+
constructor(
|
79
|
+
browser: Browser,
|
80
|
+
context: BrowserContext,
|
81
|
+
page: Page,
|
82
|
+
traceDir: string,
|
83
|
+
port: number,
|
84
|
+
debug: boolean,
|
85
|
+
storybookGlobals?: StorybookGlobals,
|
86
|
+
) {
|
69
87
|
this.#browser = browser;
|
88
|
+
this.#context = context;
|
70
89
|
this.#page = page;
|
90
|
+
this.#traceDir = traceDir;
|
71
91
|
this.#serverPort = port;
|
92
|
+
this.#debug = debug;
|
72
93
|
this.#storybookGlobals = storybookGlobals;
|
73
|
-
this.#unsubscribe = subscribeOn('shutdown', () => {
|
74
|
-
void this.closeBrowser();
|
75
|
-
});
|
76
94
|
}
|
77
95
|
|
96
|
+
// TODO Expose #browser and #context in tests
|
78
97
|
get browser() {
|
79
98
|
return this.#page;
|
80
99
|
}
|
@@ -87,19 +106,21 @@ export class InternalBrowser {
|
|
87
106
|
if (this.#isShuttingDown) return;
|
88
107
|
|
89
108
|
this.#isShuttingDown = true;
|
90
|
-
this.#unsubscribe();
|
91
109
|
|
92
110
|
try {
|
111
|
+
if (this.#debug) await this.#context.tracing.stop({ path: path.join(this.#traceDir, 'trace.zip') });
|
93
112
|
await this.#page.close();
|
113
|
+
if (this.#debug) await this.#page.video()?.saveAs(path.join(this.#traceDir, 'video.webm'));
|
114
|
+
await this.#context.close();
|
94
115
|
await this.#browser.close();
|
95
|
-
} catch
|
116
|
+
} catch {
|
96
117
|
/* noop */
|
118
|
+
} finally {
|
119
|
+
this.#closeBrowser();
|
97
120
|
}
|
98
121
|
}
|
99
122
|
|
100
123
|
async takeScreenshot(captureElement?: string | null, ignoreElements?: string | string[] | null): Promise<Buffer> {
|
101
|
-
// TODO Implement features from selenium `takeScreenshot`
|
102
|
-
// TODO Do we need scroll bar hack from selenium?
|
103
124
|
const ignore = Array.isArray(ignoreElements) ? ignoreElements : ignoreElements ? [ignoreElements] : [];
|
104
125
|
const mask = ignore.map((el) => this.#page.locator(el));
|
105
126
|
if (captureElement) {
|
@@ -186,14 +207,24 @@ export class InternalBrowser {
|
|
186
207
|
seleniumCapabilities,
|
187
208
|
playwrightOptions,
|
188
209
|
} = browserConfig;
|
210
|
+
const parsedUrl = new URL(gridUrl);
|
211
|
+
const tracesDir = path.join(
|
212
|
+
playwrightOptions?.tracesDir ?? path.join(config.reportDir, 'traces'),
|
213
|
+
process.pid.toString(),
|
214
|
+
);
|
215
|
+
const cacheDir = await getCreeveyCache();
|
216
|
+
|
217
|
+
assert(cacheDir, "Couldn't get cache directory");
|
189
218
|
|
190
219
|
let browser: Browser | null = null;
|
191
220
|
|
192
|
-
const parsedUrl = new URL(gridUrl);
|
193
221
|
if (parsedUrl.protocol === 'ws:') {
|
194
222
|
browser = await tryConnect(browsers[resolvePlaywrightBrowserType(browserConfig.browserName)], gridUrl);
|
195
223
|
} else if (parsedUrl.protocol === 'creevey:') {
|
196
|
-
browser = await browsers[resolvePlaywrightBrowserType(browserConfig.browserName)].launch(
|
224
|
+
browser = await browsers[resolvePlaywrightBrowserType(browserConfig.browserName)].launch({
|
225
|
+
...playwrightOptions,
|
226
|
+
tracesDir: path.join(cacheDir, `${process.pid}`),
|
227
|
+
});
|
197
228
|
} else {
|
198
229
|
if (browserConfig.browserName !== 'chrome') {
|
199
230
|
logger().error("Playwright's Selenium Grid feature supports only chrome browser");
|
@@ -203,20 +234,29 @@ export class InternalBrowser {
|
|
203
234
|
process.env.SELENIUM_REMOTE_URL = gridUrl;
|
204
235
|
process.env.SELENIUM_REMOTE_CAPABILITIES = JSON.stringify(seleniumCapabilities);
|
205
236
|
|
206
|
-
browser = await chromium.launch(playwrightOptions);
|
237
|
+
browser = await chromium.launch({ ...playwrightOptions, tracesDir: path.join(cacheDir, `${process.pid}`) });
|
207
238
|
}
|
208
239
|
|
209
240
|
if (!browser) {
|
210
241
|
return null;
|
211
242
|
}
|
212
243
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
244
|
+
const context = await browser.newContext({
|
245
|
+
recordVideo: options.debug
|
246
|
+
? {
|
247
|
+
dir: path.join(cacheDir, `${process.pid}`),
|
248
|
+
size: viewport,
|
249
|
+
}
|
250
|
+
: undefined,
|
251
|
+
screen: viewport,
|
252
|
+
viewport,
|
253
|
+
});
|
254
|
+
const page = await context.newPage();
|
255
|
+
if (options.debug) {
|
256
|
+
await context.tracing.start(
|
257
|
+
Object.assign({ screenshots: true, snapshots: true, sources: true }, playwrightOptions?.trace),
|
258
|
+
);
|
259
|
+
}
|
220
260
|
|
221
261
|
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
222
262
|
page.on('console', (msg) => {
|
@@ -224,15 +264,20 @@ export class InternalBrowser {
|
|
224
264
|
});
|
225
265
|
}
|
226
266
|
|
227
|
-
|
228
|
-
|
229
|
-
|
267
|
+
const internalBrowser = new InternalBrowser(
|
268
|
+
browser,
|
269
|
+
context,
|
270
|
+
page,
|
271
|
+
tracesDir,
|
272
|
+
options.port,
|
273
|
+
options.debug,
|
274
|
+
_storybookGlobals,
|
275
|
+
);
|
230
276
|
|
231
277
|
try {
|
232
278
|
if (isShuttingDown.current) return null;
|
233
279
|
const done = await internalBrowser.init({
|
234
280
|
browserName,
|
235
|
-
viewport,
|
236
281
|
storybookUrl: address,
|
237
282
|
});
|
238
283
|
|
@@ -250,15 +295,7 @@ export class InternalBrowser {
|
|
250
295
|
}
|
251
296
|
}
|
252
297
|
|
253
|
-
private async init({
|
254
|
-
browserName,
|
255
|
-
viewport,
|
256
|
-
storybookUrl,
|
257
|
-
}: {
|
258
|
-
browserName: string;
|
259
|
-
viewport?: { width: number; height: number };
|
260
|
-
storybookUrl: string;
|
261
|
-
}) {
|
298
|
+
private async init({ browserName, storybookUrl }: { browserName: string; storybookUrl: string }) {
|
262
299
|
const sessionId = this.#sessionId;
|
263
300
|
|
264
301
|
prefix.apply(logger(), {
|
@@ -278,7 +315,6 @@ export class InternalBrowser {
|
|
278
315
|
() => this.updateStorybookGlobals(),
|
279
316
|
() => this.resolveCreeveyHost(),
|
280
317
|
() => this.updateBrowserGlobalVariables(),
|
281
|
-
() => this.resizeViewport(viewport),
|
282
318
|
],
|
283
319
|
() => !this.#isShuttingDown,
|
284
320
|
);
|
@@ -291,7 +327,6 @@ export class InternalBrowser {
|
|
291
327
|
}
|
292
328
|
|
293
329
|
try {
|
294
|
-
// TODO this.#page.setDefaultNavigationTimeout(10000);
|
295
330
|
const resolvedUrl = await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
|
296
331
|
await this.#page.goto(resolvedUrl);
|
297
332
|
} catch (error) {
|
@@ -317,8 +352,7 @@ export class InternalBrowser {
|
|
317
352
|
}
|
318
353
|
|
319
354
|
private async waitForStorybook(): Promise<void> {
|
320
|
-
|
321
|
-
logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
355
|
+
logger().debug('Waiting for Storybook to initiate');
|
322
356
|
|
323
357
|
const isTimeout = await Promise.race([
|
324
358
|
new Promise<boolean>((resolve) => {
|
@@ -331,7 +365,6 @@ export class InternalBrowser {
|
|
331
365
|
do {
|
332
366
|
try {
|
333
367
|
// TODO Research a different way to ensure storybook is initiated
|
334
|
-
// TODO Maybe use `__STORYBOOK_PREVIEW__.extract()`
|
335
368
|
wait = await this.#page.evaluate((SET_GLOBALS: string) => {
|
336
369
|
if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
|
337
370
|
if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
|
@@ -347,10 +380,10 @@ export class InternalBrowser {
|
|
347
380
|
})(),
|
348
381
|
]);
|
349
382
|
|
350
|
-
|
351
|
-
if (isTimeout) throw new Error('Failed to wait `setStories` event');
|
383
|
+
if (isTimeout) throw new Error('Failed to wait Storybook init');
|
352
384
|
}
|
353
385
|
|
386
|
+
// TODO Doesn't work for some reason, maybe because of race-condition
|
354
387
|
private async triggerViteReload(): Promise<void> {
|
355
388
|
// NOTE: On the first load, Vite might try to optimize some dependencies and reload the page
|
356
389
|
// We need to trigger reload earlier to avoid unnecessary reloads further
|
@@ -410,13 +443,6 @@ export class InternalBrowser {
|
|
410
443
|
);
|
411
444
|
}
|
412
445
|
|
413
|
-
private async resizeViewport(viewport?: { width: number; height: number }): Promise<void> {
|
414
|
-
if (!viewport) return;
|
415
|
-
|
416
|
-
logger().debug('Resizing viewport to', viewport);
|
417
|
-
await this.#page.setViewportSize(viewport);
|
418
|
-
}
|
419
|
-
|
420
446
|
private async resetMousePosition(): Promise<void> {
|
421
447
|
logger().debug('Resetting mouse position to (0, 0)');
|
422
448
|
await this.#page.mouse.move(0, 0);
|
@@ -21,7 +21,7 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
|
|
21
21
|
this.#options = options;
|
22
22
|
|
23
23
|
subscribeOn('shutdown', () => {
|
24
|
-
void this.#browser?.closeBrowser().finally(() => process.exit());
|
24
|
+
void this.#browser?.closeBrowser().finally(() => setTimeout(() => process.exit(), 5_000));
|
25
25
|
this.#browser = null;
|
26
26
|
});
|
27
27
|
}
|
@@ -32,7 +32,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
|
|
32
32
|
|
33
33
|
getSessionId(): Promise<string> {
|
34
34
|
if (!this.#browser) {
|
35
|
-
// TODO Describe the error
|
36
35
|
throw new Error('Browser is not initialized');
|
37
36
|
}
|
38
37
|
|
@@ -79,7 +78,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
|
|
79
78
|
|
80
79
|
async loadStoriesFromBrowser(): Promise<StoriesRaw> {
|
81
80
|
if (!this.#browser) {
|
82
|
-
// TODO Describe the error
|
83
81
|
throw new Error('Browser is not initialized');
|
84
82
|
}
|
85
83
|
|
@@ -95,7 +93,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
|
|
95
93
|
ignoreElements?: string | string[] | null,
|
96
94
|
): Promise<Buffer> {
|
97
95
|
if (!this.#browser) {
|
98
|
-
// TODO Describe the error
|
99
96
|
throw new Error('Browser is not initialized');
|
100
97
|
}
|
101
98
|
|
@@ -104,7 +101,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
|
|
104
101
|
|
105
102
|
protected waitForComplete(callback: (isCompleted: boolean) => void): void {
|
106
103
|
if (!this.#browser) {
|
107
|
-
// TODO Describe the error
|
108
104
|
throw new Error('Browser is not initialized');
|
109
105
|
}
|
110
106
|
|
@@ -113,7 +109,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
|
|
113
109
|
|
114
110
|
protected async selectStory(id: string, waitForReady?: boolean): Promise<boolean> {
|
115
111
|
if (!this.#browser) {
|
116
|
-
// TODO Describe the error
|
117
112
|
throw new Error('Browser is not initialized');
|
118
113
|
}
|
119
114
|
|
@@ -122,7 +117,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
|
|
122
117
|
|
123
118
|
protected async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
|
124
119
|
if (!this.#browser) {
|
125
|
-
// TODO Describe the error
|
126
120
|
throw new Error('Browser is not initialized');
|
127
121
|
}
|
128
122
|
|
@@ -249,7 +249,7 @@ export class InternalBrowser {
|
|
249
249
|
const rects = await this.#browser.executeScript<
|
250
250
|
{ elementRect: ElementRect; windowRect: ElementRect } | undefined
|
251
251
|
>(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
|
252
|
-
window.scrollTo(0, 0);
|
252
|
+
window.scrollTo(0, 0);
|
253
253
|
// eslint-disable-next-line no-var
|
254
254
|
var element = document.querySelector(selector);
|
255
255
|
if (!element) return;
|
@@ -297,12 +297,10 @@ export class InternalBrowser {
|
|
297
297
|
// ? context
|
298
298
|
// ? await context.captureElementScreenshot(await element.getId())
|
299
299
|
// : await browser.findElement(By.css(captureElement)).takeScreenshot()
|
300
|
-
// :
|
301
|
-
// await takeCompositeScreenshot(browser, windowRect, elementRect);
|
300
|
+
// : await takeCompositeScreenshot(browser, windowRect, elementRect);
|
302
301
|
screenshot = isFitIntoViewport
|
303
302
|
? await this.#browser.findElement(By.css(captureElement)).takeScreenshot()
|
304
|
-
:
|
305
|
-
await this.takeCompositeScreenshot(windowRect, elementRect);
|
303
|
+
: await this.takeCompositeScreenshot(windowRect, elementRect);
|
306
304
|
|
307
305
|
logger().debug(`${chalk.cyan(captureElement)} is captured`);
|
308
306
|
}
|
@@ -503,7 +501,6 @@ export class InternalBrowser {
|
|
503
501
|
}
|
504
502
|
|
505
503
|
try {
|
506
|
-
// TODO Pageload timeout 10s
|
507
504
|
// NOTE: getUrlChecker already calls `browser.get` so we don't need another one
|
508
505
|
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
|
509
506
|
} catch (error) {
|
@@ -541,7 +538,7 @@ export class InternalBrowser {
|
|
541
538
|
}
|
542
539
|
|
543
540
|
private async waitForStorybook(): Promise<void> {
|
544
|
-
logger().debug('Waiting for
|
541
|
+
logger().debug('Waiting for Storybook to initiate');
|
545
542
|
|
546
543
|
const isTimeout = await Promise.race([
|
547
544
|
new Promise<boolean>((resolve) => {
|
@@ -554,9 +551,6 @@ export class InternalBrowser {
|
|
554
551
|
do {
|
555
552
|
// TODO Research a different way to ensure storybook is initiated
|
556
553
|
wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
|
557
|
-
// TODO Maybe use
|
558
|
-
// import { global } from '@storybook/global';
|
559
|
-
// global.IS_STORYBOOK
|
560
554
|
if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
|
561
555
|
if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
|
562
556
|
return false;
|
@@ -566,8 +560,7 @@ export class InternalBrowser {
|
|
566
560
|
})(),
|
567
561
|
]);
|
568
562
|
|
569
|
-
|
570
|
-
if (isTimeout) throw new Error('Failed to wait `setStories` event');
|
563
|
+
if (isTimeout) throw new Error('Failed to wait Storybook init');
|
571
564
|
}
|
572
565
|
|
573
566
|
private async updateStorybookGlobals(): Promise<void> {
|
@@ -42,7 +42,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
42
42
|
|
43
43
|
getSessionId(): Promise<string> {
|
44
44
|
if (!this.#browser) {
|
45
|
-
// TODO Describe the error
|
46
45
|
throw new Error('Browser is not initialized');
|
47
46
|
}
|
48
47
|
|
@@ -89,7 +88,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
89
88
|
|
90
89
|
async loadStoriesFromBrowser(): Promise<StoriesRaw> {
|
91
90
|
if (!this.#browser) {
|
92
|
-
// TODO Describe the error
|
93
91
|
throw new Error('Browser is not initialized');
|
94
92
|
}
|
95
93
|
|
@@ -98,7 +96,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
98
96
|
|
99
97
|
afterTest(test: ServerTest): Promise<void> {
|
100
98
|
if (!this.#browser) {
|
101
|
-
// TODO Describe the error
|
102
99
|
throw new Error('Browser is not initialized');
|
103
100
|
}
|
104
101
|
|
@@ -110,7 +107,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
110
107
|
ignoreElements?: string | string[] | null,
|
111
108
|
): Promise<Buffer> {
|
112
109
|
if (!this.#browser) {
|
113
|
-
// TODO Describe the error
|
114
110
|
throw new Error('Browser is not initialized');
|
115
111
|
}
|
116
112
|
|
@@ -119,7 +115,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
119
115
|
|
120
116
|
protected waitForComplete(callback: (isCompleted: boolean) => void): void {
|
121
117
|
if (!this.#browser) {
|
122
|
-
// TODO Describe the error
|
123
118
|
throw new Error('Browser is not initialized');
|
124
119
|
}
|
125
120
|
|
@@ -128,7 +123,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
128
123
|
|
129
124
|
protected async selectStory(id: string, waitForReady?: boolean): Promise<boolean> {
|
130
125
|
if (!this.#browser) {
|
131
|
-
// TODO Describe the error
|
132
126
|
throw new Error('Browser is not initialized');
|
133
127
|
}
|
134
128
|
|
@@ -137,7 +131,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
137
131
|
|
138
132
|
protected async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
|
139
133
|
if (!this.#browser) {
|
140
|
-
// TODO Describe the error
|
141
134
|
throw new Error('Browser is not initialized');
|
142
135
|
}
|
143
136
|
|