creevey 0.10.0-beta.5 → 0.10.0-beta.6
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/creevey.js +12 -5
- package/dist/creevey.js.map +1 -1
- package/dist/server/config.js +2 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/docker.js +2 -2
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +4 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +2 -1
- package/dist/server/logger.js +7 -3
- package/dist/server/logger.js.map +1 -1
- package/dist/server/master/api.js +1 -1
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/pool.d.ts +3 -3
- package/dist/server/master/pool.js +10 -65
- package/dist/server/master/pool.js.map +1 -1
- package/dist/server/master/queue.d.ts +13 -0
- package/dist/server/master/queue.js +64 -0
- package/dist/server/master/queue.js.map +1 -0
- package/dist/server/master/runner.d.ts +1 -0
- package/dist/server/master/runner.js +4 -1
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/master/server.js +1 -1
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/master/start.js +4 -4
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/internal.js +16 -19
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.js +1 -1
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/providers/browser.js +1 -1
- package/dist/server/providers/browser.js.map +1 -1
- package/dist/server/providers/hybrid.js +1 -1
- package/dist/server/providers/hybrid.js.map +1 -1
- package/dist/server/reporter.js +4 -4
- package/dist/server/reporter.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +1 -2
- package/dist/server/selenium/internal.js +93 -74
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/webdriver.js +1 -1
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/utils.d.ts +2 -1
- package/dist/server/utils.js +11 -0
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +2 -3
- package/dist/server/webdriver.js +9 -9
- package/dist/server/webdriver.js.map +1 -1
- package/dist/server/worker/chai-image.d.ts +1 -2
- package/dist/server/worker/chai-image.js +4 -3
- package/dist/server/worker/chai-image.js.map +1 -1
- package/dist/server/worker/start.js +7 -10
- package/dist/server/worker/start.js.map +1 -1
- package/dist/types.d.ts +6 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/creevey.ts +12 -6
- package/src/server/config.ts +2 -1
- package/src/server/docker.ts +2 -2
- package/src/server/index.ts +4 -4
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +18 -58
- package/src/server/master/queue.ts +64 -0
- package/src/server/master/runner.ts +4 -1
- package/src/server/master/server.ts +1 -1
- package/src/server/master/start.ts +7 -4
- package/src/server/playwright/internal.ts +16 -19
- package/src/server/playwright/webdriver.ts +1 -1
- package/src/server/providers/browser.ts +1 -1
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporter.ts +4 -3
- package/src/server/selenium/internal.ts +96 -75
- package/src/server/selenium/webdriver.ts +1 -1
- package/src/server/utils.ts +12 -1
- package/src/server/webdriver.ts +9 -15
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/start.ts +7 -11
- package/src/types.ts +6 -2
- package/.yarnrc.yml +0 -1
@@ -13,12 +13,14 @@ import {
|
|
13
13
|
TestMeta,
|
14
14
|
} from '../../types.js';
|
15
15
|
import Pool from './pool.js';
|
16
|
+
import { WorkerQueue } from './queue.js';
|
16
17
|
|
17
18
|
export default class Runner extends EventEmitter {
|
18
19
|
private failFast: boolean;
|
19
20
|
private screenDir: string;
|
20
21
|
private reportDir: string;
|
21
22
|
private browsers: string[];
|
23
|
+
private scheduler: WorkerQueue;
|
22
24
|
private pools: Record<string, Pool> = {};
|
23
25
|
tests: Partial<Record<string, ServerTest>> = {};
|
24
26
|
public get isRunning(): boolean {
|
@@ -30,9 +32,10 @@ export default class Runner extends EventEmitter {
|
|
30
32
|
this.failFast = config.failFast;
|
31
33
|
this.screenDir = config.screenDir;
|
32
34
|
this.reportDir = config.reportDir;
|
35
|
+
this.scheduler = new WorkerQueue(config.useWorkerQueue);
|
33
36
|
this.browsers = Object.keys(config.browsers);
|
34
37
|
this.browsers
|
35
|
-
.map((browser) => (this.pools[browser] = new Pool(config, browser, gridUrl)))
|
38
|
+
.map((browser) => (this.pools[browser] = new Pool(this.scheduler, config, browser, gridUrl)))
|
36
39
|
.map((pool) => pool.on('test', this.handlePoolMessage));
|
37
40
|
}
|
38
41
|
|
@@ -42,7 +42,10 @@ function outputUnnecessaryImages(imagesDir: string, images: Set<string>): void {
|
|
42
42
|
.map((imagePath) => path.posix.relative(imagesDir, imagePath))
|
43
43
|
.filter((imagePath) => !images.has(imagePath));
|
44
44
|
if (unnecessaryImages.length > 0) {
|
45
|
-
logger.warn(
|
45
|
+
logger().warn(
|
46
|
+
'We found unnecessary screenshot images, those can be safely removed:\n',
|
47
|
+
unnecessaryImages.join('\n'),
|
48
|
+
);
|
46
49
|
}
|
47
50
|
}
|
48
51
|
|
@@ -81,10 +84,10 @@ export async function start(
|
|
81
84
|
|
82
85
|
if (options.ui) {
|
83
86
|
resolveApi(creeveyApi(runner));
|
84
|
-
logger.info(`Started on http://localhost:${options.port}`);
|
87
|
+
logger().info(`Started on http://localhost:${options.port}`);
|
85
88
|
} else {
|
86
89
|
if (Object.values(runner.status.tests).filter((test) => test && !test.skip).length == 0) {
|
87
|
-
logger.warn("Don't have any tests to run");
|
90
|
+
logger().warn("Don't have any tests to run");
|
88
91
|
|
89
92
|
void shutdownWorkers().then(() => process.exit());
|
90
93
|
return;
|
@@ -101,7 +104,7 @@ export async function start(
|
|
101
104
|
void sendScreenshotsCount(config, options, runner.status)
|
102
105
|
.catch((reason: unknown) => {
|
103
106
|
const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
|
104
|
-
logger.warn(`Can't send telemetry: ${error}`);
|
107
|
+
logger().warn(`Can't send telemetry: ${error}`);
|
105
108
|
})
|
106
109
|
.finally(() => {
|
107
110
|
void shutdownWorkers().then(() => process.exit());
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { Browser, BrowserType, Page, chromium, firefox, webkit } from 'playwright-core';
|
2
|
-
import Logger from 'loglevel';
|
3
2
|
import chalk from 'chalk';
|
4
3
|
import { v4 } from 'uuid';
|
5
4
|
import prefix from 'loglevel-plugin-prefix';
|
@@ -28,7 +27,7 @@ async function tryConnect(type: BrowserType, gridUrl: string): Promise<Browser |
|
|
28
27
|
(resolve) =>
|
29
28
|
(timeout = setTimeout(() => {
|
30
29
|
isTimeout = true;
|
31
|
-
logger.error(`Can't connect to ${type.name()} playwright browser`, error);
|
30
|
+
logger().error(`Can't connect to ${type.name()} playwright browser`, error);
|
32
31
|
resolve(null);
|
33
32
|
}, 10000)),
|
34
33
|
),
|
@@ -57,13 +56,11 @@ export class InternalBrowser {
|
|
57
56
|
#sessionId: string = v4();
|
58
57
|
#serverHost: string | null = null;
|
59
58
|
#serverPort: number;
|
60
|
-
#logger: Logger.Logger;
|
61
59
|
#unsubscribe: () => void = noop;
|
62
60
|
constructor(browser: Browser, page: Page, port: number) {
|
63
61
|
this.#browser = browser;
|
64
62
|
this.#page = page;
|
65
63
|
this.#serverPort = port;
|
66
|
-
this.#logger = Logger.getLogger(this.#sessionId);
|
67
64
|
this.#unsubscribe = subscribeOn('shutdown', () => {
|
68
65
|
void this.closeBrowser();
|
69
66
|
});
|
@@ -113,7 +110,7 @@ export class InternalBrowser {
|
|
113
110
|
await this.updateBrowserGlobalVariables();
|
114
111
|
await this.resetMousePosition();
|
115
112
|
|
116
|
-
|
113
|
+
logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
|
117
114
|
|
118
115
|
const result = await this.#page.evaluate<
|
119
116
|
[error?: string | null, isCaptureCalled?: boolean] | null,
|
@@ -198,13 +195,13 @@ export class InternalBrowser {
|
|
198
195
|
break;
|
199
196
|
|
200
197
|
default:
|
201
|
-
logger.error(
|
198
|
+
logger().error(
|
202
199
|
`Unknown browser ${browserConfig.browserName}. Playwright supports browsers: chromium, firefox, webkit`,
|
203
200
|
);
|
204
201
|
}
|
205
202
|
} else {
|
206
203
|
if (browserConfig.browserName != 'chrome') {
|
207
|
-
logger.error("Playwright's Selenium Grid feature supports only chrome browser");
|
204
|
+
logger().error("Playwright's Selenium Grid feature supports only chrome browser");
|
208
205
|
return null;
|
209
206
|
}
|
210
207
|
|
@@ -242,7 +239,7 @@ export class InternalBrowser {
|
|
242
239
|
const error = new Error(`Can't load storybook root page: ${message}`);
|
243
240
|
if (originalError instanceof Error) error.stack = originalError.stack;
|
244
241
|
|
245
|
-
logger.error(error);
|
242
|
+
logger().error(error);
|
246
243
|
|
247
244
|
return null;
|
248
245
|
}
|
@@ -263,10 +260,10 @@ export class InternalBrowser {
|
|
263
260
|
}) {
|
264
261
|
const sessionId = this.#sessionId;
|
265
262
|
|
266
|
-
prefix.apply(
|
263
|
+
prefix.apply(logger(), {
|
267
264
|
format(level) {
|
268
265
|
const levelColor = colors[level.toUpperCase() as keyof typeof colors];
|
269
|
-
return `[${browserName}:${chalk.gray(
|
266
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
|
270
267
|
},
|
271
268
|
});
|
272
269
|
|
@@ -293,30 +290,30 @@ export class InternalBrowser {
|
|
293
290
|
|
294
291
|
try {
|
295
292
|
if (resolver) {
|
296
|
-
|
293
|
+
logger().debug('Resolving storybook url with custom resolver');
|
297
294
|
|
298
295
|
const resolvedUrl = await resolver();
|
299
296
|
|
300
|
-
|
297
|
+
logger().debug(`Resolver storybook url ${resolvedUrl}`);
|
301
298
|
|
302
299
|
await this.#page.goto(appendIframePath(resolvedUrl));
|
303
300
|
} else {
|
304
301
|
// TODO this.#page.setDefaultNavigationTimeout(10000);
|
305
|
-
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url)
|
302
|
+
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
|
306
303
|
}
|
307
304
|
} catch (error) {
|
308
|
-
|
305
|
+
logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
|
309
306
|
throw error;
|
310
307
|
}
|
311
308
|
}
|
312
309
|
|
313
310
|
private async checkUrl(url: string): Promise<boolean> {
|
314
311
|
try {
|
315
|
-
|
312
|
+
logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
|
316
313
|
const response = await this.#page.goto(url, { waitUntil: 'commit' });
|
317
314
|
const source = await response?.text();
|
318
315
|
|
319
|
-
|
316
|
+
logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
|
320
317
|
return source?.includes(`id="${storybookRootID}"`) ?? false;
|
321
318
|
} catch {
|
322
319
|
return false;
|
@@ -325,7 +322,7 @@ export class InternalBrowser {
|
|
325
322
|
|
326
323
|
private async waitForStorybook(): Promise<void> {
|
327
324
|
// TODO Duplicated code with selenium
|
328
|
-
|
325
|
+
logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
329
326
|
|
330
327
|
const isTimeout = await Promise.race([
|
331
328
|
new Promise<boolean>((resolve) => {
|
@@ -344,7 +341,7 @@ export class InternalBrowser {
|
|
344
341
|
return false;
|
345
342
|
}, StorybookEvents.SET_GLOBALS);
|
346
343
|
} catch (e: unknown) {
|
347
|
-
|
344
|
+
logger().debug('An error has been caught during the script:', e);
|
348
345
|
}
|
349
346
|
} while (wait);
|
350
347
|
return false;
|
@@ -358,7 +355,7 @@ export class InternalBrowser {
|
|
358
355
|
private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
|
359
356
|
if (!globals) return;
|
360
357
|
|
361
|
-
|
358
|
+
logger().debug('Applying storybook globals');
|
362
359
|
await this.#page.evaluate((globals: StorybookGlobals) => {
|
363
360
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
364
361
|
}, globals);
|
@@ -17,7 +17,7 @@ export const loadStories: StoriesProvider = async (_config, storiesListener, web
|
|
17
17
|
if (message.type == 'set') {
|
18
18
|
const { stories, oldTests } = message.payload;
|
19
19
|
if (oldTests.length > 0)
|
20
|
-
logger.warn(
|
20
|
+
logger().warn(
|
21
21
|
`If you use browser stories provider of CSFv3 Storybook feature\n` +
|
22
22
|
`Creevey will not load tests defined in story parameters from following stories:\n` +
|
23
23
|
oldTests.join('\n'),
|
@@ -54,7 +54,7 @@ async function parseParams(
|
|
54
54
|
|
55
55
|
if (listener) {
|
56
56
|
chokidar.watch(testFiles).on('change', (filePath) => {
|
57
|
-
logger.debug(`changed: ${filePath}`);
|
57
|
+
logger().debug(`changed: ${filePath}`);
|
58
58
|
|
59
59
|
// doesn't work, always returns {} due modules caching
|
60
60
|
// see https://github.com/nodejs/modules/issues/307
|
package/src/server/reporter.ts
CHANGED
@@ -22,16 +22,16 @@ export class CreeveyReporter {
|
|
22
22
|
// TODO Output in better way, like vitest, maybe
|
23
23
|
constructor(runner: EventEmitter, options: { reporterOptions: { creevey: ReporterOptions } }) {
|
24
24
|
const { sessionId, browserName } = options.reporterOptions.creevey;
|
25
|
-
const testLogger = Logger.getLogger(
|
25
|
+
const testLogger = Logger.getLogger(sessionId);
|
26
26
|
|
27
27
|
prefix.apply(testLogger, {
|
28
28
|
format(level) {
|
29
|
-
return
|
29
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${testLevels[level]} => ${chalk.gray(sessionId)}`;
|
30
30
|
},
|
31
31
|
});
|
32
32
|
|
33
33
|
runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
|
34
|
-
testLogger.warn(chalk.cyan(test.titlePath().join('/'))
|
34
|
+
testLogger.warn(chalk.cyan(test.titlePath().join('/')));
|
35
35
|
});
|
36
36
|
runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
|
37
37
|
testLogger.info(chalk.cyan(test.titlePath().join('/')), chalk.gray(`(${test.duration} ms)`));
|
@@ -39,6 +39,7 @@ export class CreeveyReporter {
|
|
39
39
|
runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error) => {
|
40
40
|
testLogger.error(
|
41
41
|
chalk.cyan(test.titlePath().join('/')),
|
42
|
+
chalk.gray(`(${test.duration} ms)`),
|
42
43
|
'\n ',
|
43
44
|
this.getErrors(
|
44
45
|
error,
|
@@ -105,7 +105,7 @@ async function buildWebdriver(
|
|
105
105
|
const url = new URL(gridUrl);
|
106
106
|
url.username = url.username ? '********' : '';
|
107
107
|
url.password = url.password ? '********' : '';
|
108
|
-
logger.debug(`
|
108
|
+
logger().debug(`Connecting to Selenium ${chalk.magenta(url.toString())}`);
|
109
109
|
|
110
110
|
// TODO Define some capabilities explicitly and define typings
|
111
111
|
const capabilities = new Capabilities({
|
@@ -125,33 +125,61 @@ async function buildWebdriver(
|
|
125
125
|
// TODO Validate browsers, versions, and platform
|
126
126
|
// TODO Use `customizeBuilder`
|
127
127
|
|
128
|
-
let webdriver: WebDriver;
|
128
|
+
let webdriver: WebDriver | null;
|
129
129
|
|
130
130
|
try {
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
131
|
+
const maxRetries = 5;
|
132
|
+
let retries = 0;
|
133
|
+
do {
|
134
|
+
webdriver = await Promise.race([
|
135
|
+
new Promise<null>((resolve) => {
|
136
|
+
setTimeout(() => {
|
137
|
+
retries += 1;
|
138
|
+
resolve(null);
|
139
|
+
}, 120_000);
|
140
|
+
}),
|
141
|
+
(async () => {
|
142
|
+
if (retries > 0) {
|
143
|
+
logger().debug(`Trying to initialize session to Selenium Grid: retried ${retries} of ${maxRetries}`);
|
144
|
+
}
|
145
|
+
const retry = retries;
|
146
|
+
// const ie = new IeOptions();
|
147
|
+
// const edge = new EdgeOptions();
|
148
|
+
// const chrome = new ChromeOptions();
|
149
|
+
// const safari = new SafariOptions();
|
150
|
+
// const firefox = new FirefoxOptions();
|
151
|
+
// edge.enableBidi();
|
152
|
+
// chrome.enableBidi();
|
153
|
+
// firefox.enableBidi();
|
154
|
+
|
155
|
+
const driver = await new Builder()
|
156
|
+
// .setIeOptions(ie)
|
157
|
+
// .setEdgeOptions(edge)
|
158
|
+
// .setChromeOptions(chrome)
|
159
|
+
// .setSafariOptions(safari)
|
160
|
+
// .setFirefoxOptions(firefox)
|
161
|
+
.usingServer(gridUrl)
|
162
|
+
.withCapabilities(capabilities)
|
163
|
+
.setLoggingPrefs(prefs) // NOTE: Should go last
|
164
|
+
.build();
|
165
|
+
|
166
|
+
// const id = await driver.getWindowHandle();
|
167
|
+
// context = await BrowsingContext(driver, { browsingContextId: id });
|
168
|
+
|
169
|
+
if (retry != retries) {
|
170
|
+
void driver.quit();
|
171
|
+
return null;
|
172
|
+
}
|
173
|
+
|
174
|
+
return driver;
|
175
|
+
})(),
|
176
|
+
]);
|
177
|
+
if (webdriver) break;
|
178
|
+
} while (retries < maxRetries);
|
179
|
+
|
180
|
+
if (!webdriver) throw new Error('Failed to initialize session to Selenium Grid due to many retries');
|
153
181
|
} catch (error) {
|
154
|
-
logger.error(`
|
182
|
+
logger().error(`Failed to start browser:`, error);
|
155
183
|
return null;
|
156
184
|
}
|
157
185
|
|
@@ -163,13 +191,11 @@ export class InternalBrowser {
|
|
163
191
|
#browser: WebDriver;
|
164
192
|
#serverHost: string | null = null;
|
165
193
|
#serverPort: number;
|
166
|
-
#logger: Logger.Logger;
|
167
194
|
#unsubscribe: () => void = noop;
|
168
195
|
#keepAliveInterval: NodeJS.Timeout | null = null;
|
169
|
-
constructor(browser: WebDriver, port: number
|
196
|
+
constructor(browser: WebDriver, port: number) {
|
170
197
|
this.#browser = browser;
|
171
198
|
this.#serverPort = port;
|
172
|
-
this.#logger = logger;
|
173
199
|
this.#unsubscribe = subscribeOn('shutdown', () => {
|
174
200
|
void this.closeBrowser();
|
175
201
|
});
|
@@ -198,7 +224,7 @@ export class InternalBrowser {
|
|
198
224
|
|
199
225
|
const ignoreStyles = await this.insertIgnoreStyles(ignoreElements);
|
200
226
|
|
201
|
-
if (
|
227
|
+
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
202
228
|
const { innerWidth, innerHeight } = await this.#browser.executeScript<{
|
203
229
|
innerWidth: number;
|
204
230
|
innerHeight: number;
|
@@ -208,16 +234,16 @@ export class InternalBrowser {
|
|
208
234
|
innerHeight: window.innerHeight,
|
209
235
|
};
|
210
236
|
});
|
211
|
-
|
237
|
+
logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
|
212
238
|
}
|
213
239
|
|
214
240
|
try {
|
215
241
|
if (!captureElement) {
|
216
|
-
|
242
|
+
logger().debug('Capturing viewport screenshot');
|
217
243
|
screenshot = await this.#browser.takeScreenshot();
|
218
|
-
|
244
|
+
logger().debug('Viewport screenshot is captured');
|
219
245
|
} else {
|
220
|
-
|
246
|
+
logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
|
221
247
|
const rects = await this.#browser.executeScript<
|
222
248
|
{ elementRect: ElementRect; windowRect: ElementRect } | undefined
|
223
249
|
>(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
|
@@ -256,11 +282,11 @@ export class InternalBrowser {
|
|
256
282
|
elementRect.height + elementRect.top <= windowRect.height;
|
257
283
|
|
258
284
|
if (isFitIntoViewport) {
|
259
|
-
|
285
|
+
logger().debug(
|
260
286
|
`Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
261
287
|
);
|
262
288
|
} else
|
263
|
-
|
289
|
+
logger().debug(
|
264
290
|
`Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
265
291
|
);
|
266
292
|
|
@@ -276,7 +302,7 @@ export class InternalBrowser {
|
|
276
302
|
: // TODO pointer-events: none, need to research
|
277
303
|
await this.takeCompositeScreenshot(windowRect, elementRect);
|
278
304
|
|
279
|
-
|
305
|
+
logger().debug(`${chalk.cyan(captureElement)} is captured`);
|
280
306
|
}
|
281
307
|
} finally {
|
282
308
|
await this.removeIgnoreStyles(ignoreStyles);
|
@@ -298,7 +324,7 @@ export class InternalBrowser {
|
|
298
324
|
await this.updateBrowserGlobalVariables();
|
299
325
|
await this.resetMousePosition();
|
300
326
|
|
301
|
-
|
327
|
+
logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
|
302
328
|
|
303
329
|
const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
|
304
330
|
function (
|
@@ -360,14 +386,14 @@ export class InternalBrowser {
|
|
360
386
|
}
|
361
387
|
|
362
388
|
async afterTest(test: ServerTest): Promise<void> {
|
363
|
-
if (
|
389
|
+
if (logger().getLevel() === Logger.levels.TRACE) {
|
364
390
|
const output: string[] = [];
|
365
391
|
const types = await this.#browser.manage().logs().getAvailableLogTypes();
|
366
392
|
for (const type of types) {
|
367
393
|
const logs = await this.#browser.manage().logs().get(type);
|
368
394
|
output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
|
369
395
|
}
|
370
|
-
|
396
|
+
logger().debug(
|
371
397
|
'----------',
|
372
398
|
getTestPath(test).join('/'),
|
373
399
|
'----------\n',
|
@@ -391,7 +417,7 @@ export class InternalBrowser {
|
|
391
417
|
|
392
418
|
if (!browser) return null;
|
393
419
|
|
394
|
-
const internalBrowser = new InternalBrowser(browser, options.port
|
420
|
+
const internalBrowser = new InternalBrowser(browser, options.port);
|
395
421
|
|
396
422
|
try {
|
397
423
|
if (isShuttingDown.current) return null;
|
@@ -409,11 +435,12 @@ export class InternalBrowser {
|
|
409
435
|
} catch (originalError) {
|
410
436
|
void internalBrowser.closeBrowser();
|
411
437
|
|
412
|
-
const message =
|
413
|
-
|
438
|
+
const message =
|
439
|
+
originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string);
|
440
|
+
const error = new Error(`Can't load storybook root page: ${message}`);
|
414
441
|
if (originalError instanceof Error) error.stack = originalError.stack;
|
415
442
|
|
416
|
-
logger.error(error);
|
443
|
+
logger().error(error);
|
417
444
|
|
418
445
|
return null;
|
419
446
|
}
|
@@ -444,16 +471,14 @@ export class InternalBrowser {
|
|
444
471
|
/* noop */
|
445
472
|
}
|
446
473
|
|
447
|
-
|
448
|
-
|
449
|
-
prefix.apply(this.#logger, {
|
474
|
+
prefix.apply(logger(), {
|
450
475
|
format(level) {
|
451
476
|
const levelColor = colors[level.toUpperCase() as keyof typeof colors];
|
452
|
-
return `[${browserName}:${chalk.gray(
|
477
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
|
453
478
|
},
|
454
479
|
});
|
455
480
|
|
456
|
-
|
481
|
+
logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
|
457
482
|
|
458
483
|
return await runSequence(
|
459
484
|
[
|
@@ -482,20 +507,20 @@ export class InternalBrowser {
|
|
482
507
|
|
483
508
|
try {
|
484
509
|
if (resolver) {
|
485
|
-
|
510
|
+
logger().debug('Resolving storybook url with custom resolver');
|
486
511
|
|
487
512
|
const resolvedUrl = await resolver();
|
488
513
|
|
489
|
-
|
514
|
+
logger().debug(`Resolver storybook url ${resolvedUrl}`);
|
490
515
|
|
491
516
|
await this.#browser.get(appendIframePath(resolvedUrl));
|
492
517
|
} else {
|
493
518
|
// TODO Pageload timeout 10s
|
494
519
|
// NOTE: getUrlChecker already calls `browser.get` so we don't need another one
|
495
|
-
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url)
|
520
|
+
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
|
496
521
|
}
|
497
522
|
} catch (error) {
|
498
|
-
|
523
|
+
logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
|
499
524
|
throw error;
|
500
525
|
}
|
501
526
|
}
|
@@ -503,13 +528,13 @@ export class InternalBrowser {
|
|
503
528
|
private async checkUrl(url: string): Promise<boolean> {
|
504
529
|
try {
|
505
530
|
// NOTE: Before trying a new url, reset the current one
|
506
|
-
|
531
|
+
logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
|
507
532
|
await openUrlAndWaitForPageSource(
|
508
533
|
this.#browser,
|
509
534
|
'about:blank',
|
510
535
|
(source: string) => !source.includes('<body></body>'),
|
511
536
|
);
|
512
|
-
|
537
|
+
logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
|
513
538
|
const source = await openUrlAndWaitForPageSource(
|
514
539
|
this.#browser,
|
515
540
|
url,
|
@@ -521,7 +546,7 @@ export class InternalBrowser {
|
|
521
546
|
// because other add significant delay and some of them don't work in earlier chrome versions
|
522
547
|
// Browsers always load page successful even it's failed
|
523
548
|
// So we just check `root` element
|
524
|
-
|
549
|
+
logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
|
525
550
|
return source.includes(`id="${storybookRootID}"`);
|
526
551
|
} catch {
|
527
552
|
return false;
|
@@ -529,7 +554,7 @@ export class InternalBrowser {
|
|
529
554
|
}
|
530
555
|
|
531
556
|
private async waitForStorybook(): Promise<void> {
|
532
|
-
|
557
|
+
logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
533
558
|
|
534
559
|
const isTimeout = await Promise.race([
|
535
560
|
new Promise<boolean>((resolve) => {
|
@@ -540,19 +565,15 @@ export class InternalBrowser {
|
|
540
565
|
(async () => {
|
541
566
|
let wait = true;
|
542
567
|
do {
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
}, StorybookEvents.SET_GLOBALS);
|
553
|
-
} catch (e: unknown) {
|
554
|
-
this.#logger.debug('An error has been caught during the script:', e);
|
555
|
-
}
|
568
|
+
// TODO Research a different way to ensure storybook is initiated
|
569
|
+
wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
|
570
|
+
// TODO Maybe use
|
571
|
+
// import { global } from '@storybook/global';
|
572
|
+
// global.IS_STORYBOOK
|
573
|
+
if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
|
574
|
+
if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
|
575
|
+
return false;
|
576
|
+
}, StorybookEvents.SET_GLOBALS);
|
556
577
|
} while (wait);
|
557
578
|
return false;
|
558
579
|
})(),
|
@@ -565,7 +586,7 @@ export class InternalBrowser {
|
|
565
586
|
private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
|
566
587
|
if (!globals) return;
|
567
588
|
|
568
|
-
|
589
|
+
logger().debug('Applying storybook globals');
|
569
590
|
await this.#browser.executeScript(function (globals: StorybookGlobals) {
|
570
591
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
571
592
|
}, globals);
|
@@ -635,7 +656,7 @@ export class InternalBrowser {
|
|
635
656
|
},
|
636
657
|
);
|
637
658
|
|
638
|
-
|
659
|
+
logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
|
639
660
|
|
640
661
|
const dWidth = windowRect.width - innerWidth;
|
641
662
|
const dHeight = windowRect.height - innerHeight;
|
@@ -649,7 +670,7 @@ export class InternalBrowser {
|
|
649
670
|
}
|
650
671
|
|
651
672
|
private async resetMousePosition(): Promise<void> {
|
652
|
-
|
673
|
+
logger().debug('Resetting mouse position to the top-left corner');
|
653
674
|
const browserName = (await this.#browser.getCapabilities()).getBrowserName();
|
654
675
|
const [browserVersion] =
|
655
676
|
(await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
|
@@ -690,7 +711,7 @@ export class InternalBrowser {
|
|
690
711
|
const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
|
691
712
|
if (!ignoreSelectors.length) return null;
|
692
713
|
|
693
|
-
|
714
|
+
logger().debug('Hiding ignored elements before capturing');
|
694
715
|
|
695
716
|
return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
|
696
717
|
return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
|
@@ -771,7 +792,7 @@ export class InternalBrowser {
|
|
771
792
|
|
772
793
|
private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
|
773
794
|
if (ignoreStyles) {
|
774
|
-
|
795
|
+
logger().debug('Revert hiding ignored elements');
|
775
796
|
await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
|
776
797
|
window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
|
777
798
|
}, ignoreStyles);
|
@@ -810,7 +831,7 @@ export class InternalBrowser {
|
|
810
831
|
this.#keepAliveInterval = setInterval(() => {
|
811
832
|
// NOTE Simple way to keep session alive
|
812
833
|
void this.#browser.getCurrentUrl().then((url) => {
|
813
|
-
logger.debug('current url', chalk.magenta(url));
|
834
|
+
logger().debug('current url', chalk.magenta(url));
|
814
835
|
});
|
815
836
|
}, 10 * 1000);
|
816
837
|
}
|