creevey 0.10.0-beta.4 → 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 +17 -20
- 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 +3 -3
- package/dist/server/reporter.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +1 -2
- package/dist/server/selenium/internal.js +105 -81
- 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 +10 -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 +17 -20
- 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 +3 -2
- package/src/server/selenium/internal.ts +107 -81
- package/src/server/selenium/webdriver.ts +1 -1
- package/src/server/utils.ts +12 -1
- package/src/server/webdriver.ts +10 -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,14 +260,13 @@ 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
|
|
273
|
-
this.#page.setDefaultNavigationTimeout(10000);
|
274
270
|
this.#page.setDefaultTimeout(60000);
|
275
271
|
|
276
272
|
return await runSequence(
|
@@ -294,29 +290,30 @@ export class InternalBrowser {
|
|
294
290
|
|
295
291
|
try {
|
296
292
|
if (resolver) {
|
297
|
-
|
293
|
+
logger().debug('Resolving storybook url with custom resolver');
|
298
294
|
|
299
295
|
const resolvedUrl = await resolver();
|
300
296
|
|
301
|
-
|
297
|
+
logger().debug(`Resolver storybook url ${resolvedUrl}`);
|
302
298
|
|
303
299
|
await this.#page.goto(appendIframePath(resolvedUrl));
|
304
300
|
} else {
|
305
|
-
|
301
|
+
// TODO this.#page.setDefaultNavigationTimeout(10000);
|
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,11 +22,11 @@ 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
|
|
@@ -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,
|
@@ -94,21 +94,25 @@ async function openUrlAndWaitForPageSource(
|
|
94
94
|
}
|
95
95
|
|
96
96
|
async function buildWebdriver(
|
97
|
-
|
97
|
+
browser: string,
|
98
98
|
gridUrl: string,
|
99
99
|
config: Config,
|
100
100
|
options: Options,
|
101
101
|
): Promise<WebDriver | null> {
|
102
|
-
const browserConfig = config.browsers[
|
103
|
-
const { /*customizeBuilder,*/ seleniumCapabilities } = browserConfig;
|
102
|
+
const browserConfig = config.browsers[browser] as BrowserConfigObject;
|
103
|
+
const { /*customizeBuilder,*/ seleniumCapabilities, browserName } = browserConfig;
|
104
104
|
|
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
|
-
const capabilities = new Capabilities({
|
111
|
+
const capabilities = new Capabilities({
|
112
|
+
browserName,
|
113
|
+
...seleniumCapabilities,
|
114
|
+
pageLoadStrategy: PageLoadStrategy.EAGER,
|
115
|
+
});
|
112
116
|
const prefs = new logging.Preferences();
|
113
117
|
|
114
118
|
if (options.trace) {
|
@@ -121,37 +125,65 @@ async function buildWebdriver(
|
|
121
125
|
// TODO Validate browsers, versions, and platform
|
122
126
|
// TODO Use `customizeBuilder`
|
123
127
|
|
124
|
-
let
|
128
|
+
let webdriver: WebDriver | null;
|
125
129
|
|
126
130
|
try {
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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');
|
149
181
|
} catch (error) {
|
150
|
-
logger.error(`
|
182
|
+
logger().error(`Failed to start browser:`, error);
|
151
183
|
return null;
|
152
184
|
}
|
153
185
|
|
154
|
-
return
|
186
|
+
return webdriver;
|
155
187
|
}
|
156
188
|
|
157
189
|
export class InternalBrowser {
|
@@ -159,13 +191,11 @@ export class InternalBrowser {
|
|
159
191
|
#browser: WebDriver;
|
160
192
|
#serverHost: string | null = null;
|
161
193
|
#serverPort: number;
|
162
|
-
#logger: Logger.Logger;
|
163
194
|
#unsubscribe: () => void = noop;
|
164
195
|
#keepAliveInterval: NodeJS.Timeout | null = null;
|
165
|
-
constructor(browser: WebDriver, port: number
|
196
|
+
constructor(browser: WebDriver, port: number) {
|
166
197
|
this.#browser = browser;
|
167
198
|
this.#serverPort = port;
|
168
|
-
this.#logger = logger;
|
169
199
|
this.#unsubscribe = subscribeOn('shutdown', () => {
|
170
200
|
void this.closeBrowser();
|
171
201
|
});
|
@@ -194,7 +224,7 @@ export class InternalBrowser {
|
|
194
224
|
|
195
225
|
const ignoreStyles = await this.insertIgnoreStyles(ignoreElements);
|
196
226
|
|
197
|
-
if (
|
227
|
+
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
198
228
|
const { innerWidth, innerHeight } = await this.#browser.executeScript<{
|
199
229
|
innerWidth: number;
|
200
230
|
innerHeight: number;
|
@@ -204,16 +234,16 @@ export class InternalBrowser {
|
|
204
234
|
innerHeight: window.innerHeight,
|
205
235
|
};
|
206
236
|
});
|
207
|
-
|
237
|
+
logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
|
208
238
|
}
|
209
239
|
|
210
240
|
try {
|
211
241
|
if (!captureElement) {
|
212
|
-
|
242
|
+
logger().debug('Capturing viewport screenshot');
|
213
243
|
screenshot = await this.#browser.takeScreenshot();
|
214
|
-
|
244
|
+
logger().debug('Viewport screenshot is captured');
|
215
245
|
} else {
|
216
|
-
|
246
|
+
logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
|
217
247
|
const rects = await this.#browser.executeScript<
|
218
248
|
{ elementRect: ElementRect; windowRect: ElementRect } | undefined
|
219
249
|
>(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
|
@@ -252,11 +282,11 @@ export class InternalBrowser {
|
|
252
282
|
elementRect.height + elementRect.top <= windowRect.height;
|
253
283
|
|
254
284
|
if (isFitIntoViewport) {
|
255
|
-
|
285
|
+
logger().debug(
|
256
286
|
`Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
257
287
|
);
|
258
288
|
} else
|
259
|
-
|
289
|
+
logger().debug(
|
260
290
|
`Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
261
291
|
);
|
262
292
|
|
@@ -272,7 +302,7 @@ export class InternalBrowser {
|
|
272
302
|
: // TODO pointer-events: none, need to research
|
273
303
|
await this.takeCompositeScreenshot(windowRect, elementRect);
|
274
304
|
|
275
|
-
|
305
|
+
logger().debug(`${chalk.cyan(captureElement)} is captured`);
|
276
306
|
}
|
277
307
|
} finally {
|
278
308
|
await this.removeIgnoreStyles(ignoreStyles);
|
@@ -294,7 +324,7 @@ export class InternalBrowser {
|
|
294
324
|
await this.updateBrowserGlobalVariables();
|
295
325
|
await this.resetMousePosition();
|
296
326
|
|
297
|
-
|
327
|
+
logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
|
298
328
|
|
299
329
|
const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
|
300
330
|
function (
|
@@ -356,14 +386,14 @@ export class InternalBrowser {
|
|
356
386
|
}
|
357
387
|
|
358
388
|
async afterTest(test: ServerTest): Promise<void> {
|
359
|
-
if (
|
389
|
+
if (logger().getLevel() === Logger.levels.TRACE) {
|
360
390
|
const output: string[] = [];
|
361
391
|
const types = await this.#browser.manage().logs().getAvailableLogTypes();
|
362
392
|
for (const type of types) {
|
363
393
|
const logs = await this.#browser.manage().logs().get(type);
|
364
394
|
output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
|
365
395
|
}
|
366
|
-
|
396
|
+
logger().debug(
|
367
397
|
'----------',
|
368
398
|
getTestPath(test).join('/'),
|
369
399
|
'----------\n',
|
@@ -387,7 +417,7 @@ export class InternalBrowser {
|
|
387
417
|
|
388
418
|
if (!browser) return null;
|
389
419
|
|
390
|
-
const internalBrowser = new InternalBrowser(browser, options.port
|
420
|
+
const internalBrowser = new InternalBrowser(browser, options.port);
|
391
421
|
|
392
422
|
try {
|
393
423
|
if (isShuttingDown.current) return null;
|
@@ -405,11 +435,12 @@ export class InternalBrowser {
|
|
405
435
|
} catch (originalError) {
|
406
436
|
void internalBrowser.closeBrowser();
|
407
437
|
|
408
|
-
const message =
|
409
|
-
|
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}`);
|
410
441
|
if (originalError instanceof Error) error.stack = originalError.stack;
|
411
442
|
|
412
|
-
logger.error(error);
|
443
|
+
logger().error(error);
|
413
444
|
|
414
445
|
return null;
|
415
446
|
}
|
@@ -440,20 +471,18 @@ export class InternalBrowser {
|
|
440
471
|
/* noop */
|
441
472
|
}
|
442
473
|
|
443
|
-
|
444
|
-
|
445
|
-
prefix.apply(this.#logger, {
|
474
|
+
prefix.apply(logger(), {
|
446
475
|
format(level) {
|
447
476
|
const levelColor = colors[level.toUpperCase() as keyof typeof colors];
|
448
|
-
return `[${browserName}:${chalk.gray(
|
477
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
|
449
478
|
},
|
450
479
|
});
|
451
480
|
|
452
|
-
|
481
|
+
logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
|
453
482
|
|
454
483
|
return await runSequence(
|
455
484
|
[
|
456
|
-
() => this.#browser.manage().setTimeouts({ pageLoad:
|
485
|
+
() => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
|
457
486
|
() => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
|
458
487
|
() => this.waitForStorybook(),
|
459
488
|
() => this.updateStorybookGlobals(storybookGlobals),
|
@@ -478,19 +507,20 @@ export class InternalBrowser {
|
|
478
507
|
|
479
508
|
try {
|
480
509
|
if (resolver) {
|
481
|
-
|
510
|
+
logger().debug('Resolving storybook url with custom resolver');
|
482
511
|
|
483
512
|
const resolvedUrl = await resolver();
|
484
513
|
|
485
|
-
|
514
|
+
logger().debug(`Resolver storybook url ${resolvedUrl}`);
|
486
515
|
|
487
516
|
await this.#browser.get(appendIframePath(resolvedUrl));
|
488
517
|
} else {
|
518
|
+
// TODO Pageload timeout 10s
|
489
519
|
// NOTE: getUrlChecker already calls `browser.get` so we don't need another one
|
490
|
-
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url)
|
520
|
+
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
|
491
521
|
}
|
492
522
|
} catch (error) {
|
493
|
-
|
523
|
+
logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
|
494
524
|
throw error;
|
495
525
|
}
|
496
526
|
}
|
@@ -498,13 +528,13 @@ export class InternalBrowser {
|
|
498
528
|
private async checkUrl(url: string): Promise<boolean> {
|
499
529
|
try {
|
500
530
|
// NOTE: Before trying a new url, reset the current one
|
501
|
-
|
531
|
+
logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
|
502
532
|
await openUrlAndWaitForPageSource(
|
503
533
|
this.#browser,
|
504
534
|
'about:blank',
|
505
535
|
(source: string) => !source.includes('<body></body>'),
|
506
536
|
);
|
507
|
-
|
537
|
+
logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
|
508
538
|
const source = await openUrlAndWaitForPageSource(
|
509
539
|
this.#browser,
|
510
540
|
url,
|
@@ -516,7 +546,7 @@ export class InternalBrowser {
|
|
516
546
|
// because other add significant delay and some of them don't work in earlier chrome versions
|
517
547
|
// Browsers always load page successful even it's failed
|
518
548
|
// So we just check `root` element
|
519
|
-
|
549
|
+
logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
|
520
550
|
return source.includes(`id="${storybookRootID}"`);
|
521
551
|
} catch {
|
522
552
|
return false;
|
@@ -524,7 +554,7 @@ export class InternalBrowser {
|
|
524
554
|
}
|
525
555
|
|
526
556
|
private async waitForStorybook(): Promise<void> {
|
527
|
-
|
557
|
+
logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
528
558
|
|
529
559
|
const isTimeout = await Promise.race([
|
530
560
|
new Promise<boolean>((resolve) => {
|
@@ -535,19 +565,15 @@ export class InternalBrowser {
|
|
535
565
|
(async () => {
|
536
566
|
let wait = true;
|
537
567
|
do {
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
}, StorybookEvents.SET_GLOBALS);
|
548
|
-
} catch (e: unknown) {
|
549
|
-
this.#logger.debug('An error has been caught during the script:', e);
|
550
|
-
}
|
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);
|
551
577
|
} while (wait);
|
552
578
|
return false;
|
553
579
|
})(),
|
@@ -560,7 +586,7 @@ export class InternalBrowser {
|
|
560
586
|
private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
|
561
587
|
if (!globals) return;
|
562
588
|
|
563
|
-
|
589
|
+
logger().debug('Applying storybook globals');
|
564
590
|
await this.#browser.executeScript(function (globals: StorybookGlobals) {
|
565
591
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
566
592
|
}, globals);
|
@@ -630,7 +656,7 @@ export class InternalBrowser {
|
|
630
656
|
},
|
631
657
|
);
|
632
658
|
|
633
|
-
|
659
|
+
logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
|
634
660
|
|
635
661
|
const dWidth = windowRect.width - innerWidth;
|
636
662
|
const dHeight = windowRect.height - innerHeight;
|
@@ -644,7 +670,7 @@ export class InternalBrowser {
|
|
644
670
|
}
|
645
671
|
|
646
672
|
private async resetMousePosition(): Promise<void> {
|
647
|
-
|
673
|
+
logger().debug('Resetting mouse position to the top-left corner');
|
648
674
|
const browserName = (await this.#browser.getCapabilities()).getBrowserName();
|
649
675
|
const [browserVersion] =
|
650
676
|
(await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
|
@@ -685,7 +711,7 @@ export class InternalBrowser {
|
|
685
711
|
const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
|
686
712
|
if (!ignoreSelectors.length) return null;
|
687
713
|
|
688
|
-
|
714
|
+
logger().debug('Hiding ignored elements before capturing');
|
689
715
|
|
690
716
|
return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
|
691
717
|
return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
|
@@ -766,7 +792,7 @@ export class InternalBrowser {
|
|
766
792
|
|
767
793
|
private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
|
768
794
|
if (ignoreStyles) {
|
769
|
-
|
795
|
+
logger().debug('Revert hiding ignored elements');
|
770
796
|
await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
|
771
797
|
window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
|
772
798
|
}, ignoreStyles);
|
@@ -805,7 +831,7 @@ export class InternalBrowser {
|
|
805
831
|
this.#keepAliveInterval = setInterval(() => {
|
806
832
|
// NOTE Simple way to keep session alive
|
807
833
|
void this.#browser.getCurrentUrl().then((url) => {
|
808
|
-
logger.debug('current url', chalk.magenta(url));
|
834
|
+
logger().debug('current url', chalk.magenta(url));
|
809
835
|
});
|
810
836
|
}, 10 * 1000);
|
811
837
|
}
|