creevey 0.10.0-beta.0 → 0.10.0-beta.10
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/addon/components/Panel.js +2 -2
- package/dist/client/addon/components/Panel.js.map +1 -1
- package/dist/client/addon/controller.js +4 -5
- package/dist/client/addon/controller.js.map +1 -1
- package/dist/client/addon/withCreevey.js +18 -34
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.js +12 -0
- package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
- package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.js +23 -5
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/web/CreeveyApp.js +22 -6
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +5 -0
- package/dist/client/web/CreeveyContext.js +3 -0
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +2 -2
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +1 -0
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +49 -6
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +1 -3
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +1 -3
- package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
- package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
- package/dist/client/web/KeyboardEventsContext.js +62 -57
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/{index-DkmZfG9C.js → index-BE9CL5_G.js} +94 -94
- package/dist/client/web/index.html +1 -1
- package/dist/creevey.js +13 -5
- package/dist/creevey.js.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/playwright.d.ts +2 -0
- package/dist/playwright.js +6 -0
- package/dist/playwright.js.map +1 -0
- package/dist/selenium.d.ts +2 -0
- package/dist/selenium.js +6 -0
- package/dist/selenium.js.map +1 -0
- package/dist/server/config.d.ts +1 -1
- package/dist/server/config.js +12 -5
- 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 +40 -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 -63
- 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/docker-file.js +12 -1
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/docker.d.ts +1 -1
- package/dist/server/playwright/docker.js +1 -6
- package/dist/server/playwright/docker.js.map +1 -1
- package/dist/server/playwright/internal.d.ts +2 -2
- package/dist/server/playwright/internal.js +56 -44
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.d.ts +2 -1
- package/dist/server/playwright/webdriver.js +4 -1
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/providers/browser.js +2 -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 +3 -3
- package/dist/server/selenium/internal.js +126 -89
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +2 -2
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.d.ts +2 -1
- package/dist/server/selenium/webdriver.js +8 -1
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/telemetry.js +7 -3
- package/dist/server/telemetry.js.map +1 -1
- package/dist/server/utils.d.ts +2 -1
- package/dist/server/utils.js +13 -3
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +4 -4
- 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 +27 -39
- package/dist/server/worker/start.js.map +1 -1
- package/dist/types.d.ts +32 -21
- package/dist/types.js +13 -1
- package/dist/types.js.map +1 -1
- package/package.json +45 -38
- package/playwright.d.ts +2 -0
- package/selenium.d.ts +2 -0
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/controller.ts +13 -6
- package/src/client/addon/withCreevey.ts +25 -13
- package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
- package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
- package/src/client/shared/components/ResultsPage.tsx +28 -7
- package/src/client/web/CreeveyApp.tsx +25 -7
- package/src/client/web/CreeveyContext.tsx +9 -0
- package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
- package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
- package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
- package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
- package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
- package/src/client/web/KeyboardEventsContext.tsx +61 -73
- package/src/creevey.ts +13 -6
- package/src/index.ts +0 -2
- package/src/playwright.ts +1 -0
- package/src/selenium.ts +1 -0
- package/src/server/config.ts +19 -7
- package/src/server/docker.ts +2 -2
- package/src/server/index.ts +45 -4
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +18 -56
- 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/docker-file.ts +14 -1
- package/src/server/playwright/docker.ts +2 -12
- package/src/server/playwright/internal.ts +76 -49
- package/src/server/playwright/webdriver.ts +7 -2
- package/src/server/providers/browser.ts +2 -1
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporter.ts +4 -3
- package/src/server/selenium/internal.ts +147 -93
- package/src/server/selenium/selenoid.ts +2 -2
- package/src/server/selenium/webdriver.ts +12 -2
- package/src/server/telemetry.ts +7 -3
- package/src/server/utils.ts +14 -4
- package/src/server/webdriver.ts +13 -15
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/start.ts +29 -48
- package/src/types.ts +35 -23
- package/types/playwright-context.d.ts +7 -0
- package/types/selenium-context.d.ts +7 -0
- package/.yarnrc.yml +0 -1
- package/chromatic.config.json +0 -5
@@ -1,5 +1,4 @@
|
|
1
1
|
import { Args } from '@storybook/csf';
|
2
|
-
import { SET_GLOBALS, UPDATE_STORY_ARGS, STORY_RENDERED } from '@storybook/core-events';
|
3
2
|
import chalk from 'chalk';
|
4
3
|
import http from 'http';
|
5
4
|
import https from 'https';
|
@@ -13,10 +12,20 @@ import { Builder, By, Capabilities, Origin, WebDriver, WebElement, logging } fro
|
|
13
12
|
// import { Options as SafariOptions } from 'selenium-webdriver/safari';
|
14
13
|
// import { Options as FirefoxOptions } from 'selenium-webdriver/firefox';
|
15
14
|
import { PageLoadStrategy } from 'selenium-webdriver/lib/capabilities.js';
|
16
|
-
import {
|
15
|
+
import {
|
16
|
+
BrowserConfigObject,
|
17
|
+
Config,
|
18
|
+
noop,
|
19
|
+
StorybookGlobals,
|
20
|
+
StoryInput,
|
21
|
+
StoriesRaw,
|
22
|
+
Options,
|
23
|
+
ServerTest,
|
24
|
+
StorybookEvents,
|
25
|
+
} from '../../types.js';
|
17
26
|
import { colors, logger } from '../logger.js';
|
18
27
|
import { subscribeOn } from '../messages.js';
|
19
|
-
import { isShuttingDown, runSequence } from '../utils.js';
|
28
|
+
import { getTestPath, isShuttingDown, runSequence } from '../utils.js';
|
20
29
|
import {
|
21
30
|
appendIframePath,
|
22
31
|
getAddresses,
|
@@ -85,21 +94,25 @@ async function openUrlAndWaitForPageSource(
|
|
85
94
|
}
|
86
95
|
|
87
96
|
async function buildWebdriver(
|
88
|
-
|
97
|
+
browser: string,
|
89
98
|
gridUrl: string,
|
90
99
|
config: Config,
|
91
100
|
options: Options,
|
92
101
|
): Promise<WebDriver | null> {
|
93
|
-
const browserConfig = config.browsers[
|
94
|
-
const {
|
102
|
+
const browserConfig = config.browsers[browser] as BrowserConfigObject;
|
103
|
+
const { /*customizeBuilder,*/ seleniumCapabilities, browserName } = browserConfig;
|
95
104
|
|
96
105
|
const url = new URL(gridUrl);
|
97
106
|
url.username = url.username ? '********' : '';
|
98
107
|
url.password = url.password ? '********' : '';
|
99
|
-
logger.debug(`
|
108
|
+
logger().debug(`Connecting to Selenium ${chalk.magenta(url.toString())}`);
|
100
109
|
|
101
110
|
// TODO Define some capabilities explicitly and define typings
|
102
|
-
const capabilities = new Capabilities({
|
111
|
+
const capabilities = new Capabilities({
|
112
|
+
browserName,
|
113
|
+
...seleniumCapabilities,
|
114
|
+
pageLoadStrategy: PageLoadStrategy.EAGER,
|
115
|
+
});
|
103
116
|
const prefs = new logging.Preferences();
|
104
117
|
|
105
118
|
if (options.trace) {
|
@@ -112,37 +125,65 @@ async function buildWebdriver(
|
|
112
125
|
// TODO Validate browsers, versions, and platform
|
113
126
|
// TODO Use `customizeBuilder`
|
114
127
|
|
115
|
-
let
|
128
|
+
let webdriver: WebDriver | null;
|
116
129
|
|
117
130
|
try {
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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');
|
140
181
|
} catch (error) {
|
141
|
-
logger.error(`
|
182
|
+
logger().error(`Failed to start browser:`, error);
|
142
183
|
return null;
|
143
184
|
}
|
144
185
|
|
145
|
-
return
|
186
|
+
return webdriver;
|
146
187
|
}
|
147
188
|
|
148
189
|
export class InternalBrowser {
|
@@ -150,13 +191,13 @@ export class InternalBrowser {
|
|
150
191
|
#browser: WebDriver;
|
151
192
|
#serverHost: string | null = null;
|
152
193
|
#serverPort: number;
|
153
|
-
#
|
194
|
+
#storybookGlobals?: StorybookGlobals;
|
154
195
|
#unsubscribe: () => void = noop;
|
155
196
|
#keepAliveInterval: NodeJS.Timeout | null = null;
|
156
|
-
constructor(browser: WebDriver, port: number,
|
197
|
+
constructor(browser: WebDriver, port: number, storybookGlobals?: StorybookGlobals) {
|
157
198
|
this.#browser = browser;
|
158
199
|
this.#serverPort = port;
|
159
|
-
this.#
|
200
|
+
this.#storybookGlobals = storybookGlobals;
|
160
201
|
this.#unsubscribe = subscribeOn('shutdown', () => {
|
161
202
|
void this.closeBrowser();
|
162
203
|
});
|
@@ -185,7 +226,7 @@ export class InternalBrowser {
|
|
185
226
|
|
186
227
|
const ignoreStyles = await this.insertIgnoreStyles(ignoreElements);
|
187
228
|
|
188
|
-
if (
|
229
|
+
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
189
230
|
const { innerWidth, innerHeight } = await this.#browser.executeScript<{
|
190
231
|
innerWidth: number;
|
191
232
|
innerHeight: number;
|
@@ -195,16 +236,16 @@ export class InternalBrowser {
|
|
195
236
|
innerHeight: window.innerHeight,
|
196
237
|
};
|
197
238
|
});
|
198
|
-
|
239
|
+
logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
|
199
240
|
}
|
200
241
|
|
201
242
|
try {
|
202
243
|
if (!captureElement) {
|
203
|
-
|
244
|
+
logger().debug('Capturing viewport screenshot');
|
204
245
|
screenshot = await this.#browser.takeScreenshot();
|
205
|
-
|
246
|
+
logger().debug('Viewport screenshot is captured');
|
206
247
|
} else {
|
207
|
-
|
248
|
+
logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
|
208
249
|
const rects = await this.#browser.executeScript<
|
209
250
|
{ elementRect: ElementRect; windowRect: ElementRect } | undefined
|
210
251
|
>(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
|
@@ -243,11 +284,11 @@ export class InternalBrowser {
|
|
243
284
|
elementRect.height + elementRect.top <= windowRect.height;
|
244
285
|
|
245
286
|
if (isFitIntoViewport) {
|
246
|
-
|
287
|
+
logger().debug(
|
247
288
|
`Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
248
289
|
);
|
249
290
|
} else
|
250
|
-
|
291
|
+
logger().debug(
|
251
292
|
`Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
252
293
|
);
|
253
294
|
|
@@ -263,7 +304,7 @@ export class InternalBrowser {
|
|
263
304
|
: // TODO pointer-events: none, need to research
|
264
305
|
await this.takeCompositeScreenshot(windowRect, elementRect);
|
265
306
|
|
266
|
-
|
307
|
+
logger().debug(`${chalk.cyan(captureElement)} is captured`);
|
267
308
|
}
|
268
309
|
} finally {
|
269
310
|
await this.removeIgnoreStyles(ignoreStyles);
|
@@ -282,10 +323,11 @@ export class InternalBrowser {
|
|
282
323
|
|
283
324
|
async selectStory(id: string, waitForReady = false): Promise<boolean> {
|
284
325
|
// NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
|
326
|
+
await this.updateStorybookGlobals();
|
285
327
|
await this.updateBrowserGlobalVariables();
|
286
328
|
await this.resetMousePosition();
|
287
329
|
|
288
|
-
|
330
|
+
logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
|
289
331
|
|
290
332
|
const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
|
291
333
|
function (
|
@@ -329,8 +371,8 @@ export class InternalBrowser {
|
|
329
371
|
},
|
330
372
|
story.id,
|
331
373
|
updatedArgs,
|
332
|
-
UPDATE_STORY_ARGS,
|
333
|
-
STORY_RENDERED,
|
374
|
+
StorybookEvents.UPDATE_STORY_ARGS,
|
375
|
+
StorybookEvents.STORY_RENDERED,
|
334
376
|
);
|
335
377
|
}
|
336
378
|
|
@@ -346,6 +388,24 @@ export class InternalBrowser {
|
|
346
388
|
return stories;
|
347
389
|
}
|
348
390
|
|
391
|
+
async afterTest(test: ServerTest): Promise<void> {
|
392
|
+
if (logger().getLevel() === Logger.levels.TRACE) {
|
393
|
+
const output: string[] = [];
|
394
|
+
const types = await this.#browser.manage().logs().getAvailableLogTypes();
|
395
|
+
for (const type of types) {
|
396
|
+
const logs = await this.#browser.manage().logs().get(type);
|
397
|
+
output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
|
398
|
+
}
|
399
|
+
logger().debug(
|
400
|
+
'----------',
|
401
|
+
getTestPath(test).join('/'),
|
402
|
+
'----------\n',
|
403
|
+
output.join('\n'),
|
404
|
+
'\n----------------------------------------------------------------------------------------------------',
|
405
|
+
);
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
349
409
|
static async getBrowser(
|
350
410
|
browserName: string,
|
351
411
|
gridUrl: string,
|
@@ -360,7 +420,7 @@ export class InternalBrowser {
|
|
360
420
|
|
361
421
|
if (!browser) return null;
|
362
422
|
|
363
|
-
const internalBrowser = new InternalBrowser(browser, options.port,
|
423
|
+
const internalBrowser = new InternalBrowser(browser, options.port, _storybookGlobals);
|
364
424
|
|
365
425
|
try {
|
366
426
|
if (isShuttingDown.current) return null;
|
@@ -370,7 +430,6 @@ export class InternalBrowser {
|
|
370
430
|
gridUrl,
|
371
431
|
viewport,
|
372
432
|
storybookUrl: address,
|
373
|
-
storybookGlobals: _storybookGlobals,
|
374
433
|
resolveStorybookUrl: config.resolveStorybookUrl,
|
375
434
|
});
|
376
435
|
|
@@ -378,11 +437,12 @@ export class InternalBrowser {
|
|
378
437
|
} catch (originalError) {
|
379
438
|
void internalBrowser.closeBrowser();
|
380
439
|
|
381
|
-
const message =
|
382
|
-
|
440
|
+
const message =
|
441
|
+
originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string);
|
442
|
+
const error = new Error(`Can't load storybook root page: ${message}`);
|
383
443
|
if (originalError instanceof Error) error.stack = originalError.stack;
|
384
444
|
|
385
|
-
logger.error(error);
|
445
|
+
logger().error(error);
|
386
446
|
|
387
447
|
return null;
|
388
448
|
}
|
@@ -393,14 +453,12 @@ export class InternalBrowser {
|
|
393
453
|
gridUrl,
|
394
454
|
viewport,
|
395
455
|
storybookUrl,
|
396
|
-
storybookGlobals,
|
397
456
|
resolveStorybookUrl,
|
398
457
|
}: {
|
399
458
|
browserName: string;
|
400
459
|
gridUrl: string;
|
401
460
|
viewport?: { width: number; height: number };
|
402
461
|
storybookUrl: string;
|
403
|
-
storybookGlobals?: StorybookGlobals;
|
404
462
|
resolveStorybookUrl?: () => Promise<string>;
|
405
463
|
}): Promise<boolean> {
|
406
464
|
const sessionId = (await this.#browser.getSession()).getId();
|
@@ -413,23 +471,21 @@ export class InternalBrowser {
|
|
413
471
|
/* noop */
|
414
472
|
}
|
415
473
|
|
416
|
-
|
417
|
-
|
418
|
-
prefix.apply(this.#logger, {
|
474
|
+
prefix.apply(logger(), {
|
419
475
|
format(level) {
|
420
476
|
const levelColor = colors[level.toUpperCase() as keyof typeof colors];
|
421
|
-
return `[${browserName}:${chalk.gray(
|
477
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
|
422
478
|
},
|
423
479
|
});
|
424
480
|
|
425
|
-
|
481
|
+
logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
|
426
482
|
|
427
483
|
return await runSequence(
|
428
484
|
[
|
429
|
-
() => this.#browser.manage().setTimeouts({ pageLoad:
|
485
|
+
() => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
|
430
486
|
() => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
|
431
487
|
() => this.waitForStorybook(),
|
432
|
-
() => this.updateStorybookGlobals(
|
488
|
+
() => this.updateStorybookGlobals(),
|
433
489
|
() => this.resolveCreeveyHost(),
|
434
490
|
() => this.updateBrowserGlobalVariables(),
|
435
491
|
// NOTE: Selenium draws automation toolbar with some delay after webdriver initialization
|
@@ -451,19 +507,20 @@ export class InternalBrowser {
|
|
451
507
|
|
452
508
|
try {
|
453
509
|
if (resolver) {
|
454
|
-
|
510
|
+
logger().debug('Resolving storybook url with custom resolver');
|
455
511
|
|
456
512
|
const resolvedUrl = await resolver();
|
457
513
|
|
458
|
-
|
514
|
+
logger().debug(`Resolver storybook url ${resolvedUrl}`);
|
459
515
|
|
460
516
|
await this.#browser.get(appendIframePath(resolvedUrl));
|
461
517
|
} else {
|
518
|
+
// TODO Pageload timeout 10s
|
462
519
|
// NOTE: getUrlChecker already calls `browser.get` so we don't need another one
|
463
|
-
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url)
|
520
|
+
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
|
464
521
|
}
|
465
522
|
} catch (error) {
|
466
|
-
|
523
|
+
logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
|
467
524
|
throw error;
|
468
525
|
}
|
469
526
|
}
|
@@ -471,13 +528,13 @@ export class InternalBrowser {
|
|
471
528
|
private async checkUrl(url: string): Promise<boolean> {
|
472
529
|
try {
|
473
530
|
// NOTE: Before trying a new url, reset the current one
|
474
|
-
|
531
|
+
logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
|
475
532
|
await openUrlAndWaitForPageSource(
|
476
533
|
this.#browser,
|
477
534
|
'about:blank',
|
478
535
|
(source: string) => !source.includes('<body></body>'),
|
479
536
|
);
|
480
|
-
|
537
|
+
logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
|
481
538
|
const source = await openUrlAndWaitForPageSource(
|
482
539
|
this.#browser,
|
483
540
|
url,
|
@@ -489,7 +546,7 @@ export class InternalBrowser {
|
|
489
546
|
// because other add significant delay and some of them don't work in earlier chrome versions
|
490
547
|
// Browsers always load page successful even it's failed
|
491
548
|
// So we just check `root` element
|
492
|
-
|
549
|
+
logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
|
493
550
|
return source.includes(`id="${storybookRootID}"`);
|
494
551
|
} catch {
|
495
552
|
return false;
|
@@ -497,7 +554,7 @@ export class InternalBrowser {
|
|
497
554
|
}
|
498
555
|
|
499
556
|
private async waitForStorybook(): Promise<void> {
|
500
|
-
|
557
|
+
logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
501
558
|
|
502
559
|
const isTimeout = await Promise.race([
|
503
560
|
new Promise<boolean>((resolve) => {
|
@@ -508,19 +565,15 @@ export class InternalBrowser {
|
|
508
565
|
(async () => {
|
509
566
|
let wait = true;
|
510
567
|
do {
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
}, SET_GLOBALS);
|
521
|
-
} catch (e: unknown) {
|
522
|
-
this.#logger.debug('An error has been caught during the script:', e);
|
523
|
-
}
|
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);
|
524
577
|
} while (wait);
|
525
578
|
return false;
|
526
579
|
})(),
|
@@ -530,13 +583,13 @@ export class InternalBrowser {
|
|
530
583
|
if (isTimeout) throw new Error('Failed to wait `setStories` event');
|
531
584
|
}
|
532
585
|
|
533
|
-
private async updateStorybookGlobals(
|
534
|
-
if (!
|
586
|
+
private async updateStorybookGlobals(): Promise<void> {
|
587
|
+
if (!this.#storybookGlobals) return;
|
535
588
|
|
536
|
-
|
589
|
+
logger().debug('Applying storybook globals');
|
537
590
|
await this.#browser.executeScript(function (globals: StorybookGlobals) {
|
538
591
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
539
|
-
},
|
592
|
+
}, this.#storybookGlobals);
|
540
593
|
}
|
541
594
|
|
542
595
|
private async resolveCreeveyHost(): Promise<void> {
|
@@ -603,7 +656,7 @@ export class InternalBrowser {
|
|
603
656
|
},
|
604
657
|
);
|
605
658
|
|
606
|
-
|
659
|
+
logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
|
607
660
|
|
608
661
|
const dWidth = windowRect.width - innerWidth;
|
609
662
|
const dHeight = windowRect.height - innerHeight;
|
@@ -617,7 +670,7 @@ export class InternalBrowser {
|
|
617
670
|
}
|
618
671
|
|
619
672
|
private async resetMousePosition(): Promise<void> {
|
620
|
-
|
673
|
+
logger().debug('Resetting mouse position to the top-left corner');
|
621
674
|
const browserName = (await this.#browser.getCapabilities()).getBrowserName();
|
622
675
|
const [browserVersion] =
|
623
676
|
(await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
|
@@ -645,8 +698,9 @@ export class InternalBrowser {
|
|
645
698
|
y: Math.ceil((-1 * height) / 2) - top,
|
646
699
|
})
|
647
700
|
.perform();
|
648
|
-
} else if (browserName == 'firefox'
|
701
|
+
} else if (browserName == 'firefox') {
|
649
702
|
// NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad:
|
703
|
+
// NOTE In recent versions (eg 128.0) moving by 0 x 0 doesn't work at all
|
650
704
|
await this.#browser.actions().move({ origin: Origin.VIEWPORT, x: 0, y: 1 }).perform();
|
651
705
|
} else {
|
652
706
|
// NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine
|
@@ -658,7 +712,7 @@ export class InternalBrowser {
|
|
658
712
|
const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
|
659
713
|
if (!ignoreSelectors.length) return null;
|
660
714
|
|
661
|
-
|
715
|
+
logger().debug('Hiding ignored elements before capturing');
|
662
716
|
|
663
717
|
return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
|
664
718
|
return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
|
@@ -734,12 +788,12 @@ export class InternalBrowser {
|
|
734
788
|
compositeImage.data[i + 3] = image.data[j + 3];
|
735
789
|
}
|
736
790
|
}
|
737
|
-
return compositeImage
|
791
|
+
return PNG.sync.write(compositeImage);
|
738
792
|
}
|
739
793
|
|
740
794
|
private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
|
741
795
|
if (ignoreStyles) {
|
742
|
-
|
796
|
+
logger().debug('Revert hiding ignored elements');
|
743
797
|
await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
|
744
798
|
window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
|
745
799
|
}, ignoreStyles);
|
@@ -778,7 +832,7 @@ export class InternalBrowser {
|
|
778
832
|
this.#keepAliveInterval = setInterval(() => {
|
779
833
|
// NOTE Simple way to keep session alive
|
780
834
|
void this.#browser.getCurrentUrl().then((url) => {
|
781
|
-
logger.debug('current url', chalk.magenta(url));
|
835
|
+
logger().debug('current url', chalk.magenta(url));
|
782
836
|
});
|
783
837
|
}, 10 * 1000);
|
784
838
|
}
|
@@ -30,7 +30,7 @@ async function createSelenoidConfig(
|
|
30
30
|
browsers.forEach(
|
31
31
|
({
|
32
32
|
browserName,
|
33
|
-
browserVersion = 'latest',
|
33
|
+
seleniumCapabilities: { browserVersion = 'latest' } = {},
|
34
34
|
dockerImage = `selenoid/${browserName}:${browserVersion}`,
|
35
35
|
webdriverCommand = [],
|
36
36
|
}) => {
|
@@ -117,7 +117,7 @@ export async function startSelenoidContainer(config: Config, debug: boolean): Pr
|
|
117
117
|
browsers.forEach(
|
118
118
|
({
|
119
119
|
browserName,
|
120
|
-
browserVersion = 'latest',
|
120
|
+
seleniumCapabilities: { browserVersion = 'latest' } = {},
|
121
121
|
limit: browserLimit = 1,
|
122
122
|
dockerImage = `selenoid/${browserName}:${browserVersion}`,
|
123
123
|
}) => {
|
@@ -1,5 +1,6 @@
|
|
1
|
+
/// <reference types="../../../types/selenium-context" />
|
1
2
|
import { Args } from '@storybook/csf';
|
2
|
-
import { Config, StorybookGlobals, StoryInput, StoriesRaw, Options } from '../../types.js';
|
3
|
+
import { Config, StorybookGlobals, StoryInput, StoriesRaw, Options, ServerTest } from '../../types.js';
|
3
4
|
import { subscribeOn } from '../messages.js';
|
4
5
|
import { CreeveyWebdriverBase } from '../webdriver.js';
|
5
6
|
import type { InternalBrowser } from './internal.js';
|
@@ -62,7 +63,7 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
62
63
|
try {
|
63
64
|
return await import('./internal.js');
|
64
65
|
} catch (error) {
|
65
|
-
logger.error(error);
|
66
|
+
logger().error(error);
|
66
67
|
return null;
|
67
68
|
}
|
68
69
|
})();
|
@@ -95,6 +96,15 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
95
96
|
return this.#browser.loadStoriesFromBrowser();
|
96
97
|
}
|
97
98
|
|
99
|
+
afterTest(test: ServerTest): Promise<void> {
|
100
|
+
if (!this.#browser) {
|
101
|
+
// TODO Describe the error
|
102
|
+
throw new Error('Browser is not initialized');
|
103
|
+
}
|
104
|
+
|
105
|
+
return this.#browser.afterTest(test);
|
106
|
+
}
|
107
|
+
|
98
108
|
protected async takeScreenshot(
|
99
109
|
captureElement: string | null,
|
100
110
|
ignoreElements?: string | string[] | null,
|
package/src/server/telemetry.ts
CHANGED
@@ -154,12 +154,16 @@ export async function sendScreenshotsCount(
|
|
154
154
|
name: name,
|
155
155
|
gridUrl: browser.gridUrl ? sanitizeGridUrl(browser.gridUrl) : undefined,
|
156
156
|
browserName: browser.browserName,
|
157
|
-
|
158
|
-
|
157
|
+
// @ts-expect-error Support old config version
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
159
|
+
browserVersion: browser.seleniumCapabilities?.browserVersion ?? browser.browserVersion,
|
160
|
+
// @ts-expect-error Support old config version
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
162
|
+
platformName: browser.seleniumCapabilities?.platformName ?? browser.platformName,
|
159
163
|
viewport: browser.viewport,
|
160
164
|
limit: browser.limit,
|
161
165
|
dockerImage: browser.dockerImage,
|
162
|
-
'se:teamname': browser['se:teamname'],
|
166
|
+
'se:teamname': browser.seleniumCapabilities?.['se:teamname'],
|
163
167
|
}
|
164
168
|
: browser,
|
165
169
|
]),
|
package/src/server/utils.ts
CHANGED
@@ -3,10 +3,9 @@ import { get } from 'https';
|
|
3
3
|
import cluster from 'cluster';
|
4
4
|
import { dirname } from 'path';
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'url';
|
6
|
-
import { createRequire } from 'module';
|
7
6
|
import { register as esmRegister } from 'tsx/esm/api';
|
8
7
|
import { register as cjsRegister } from 'tsx/cjs/api';
|
9
|
-
import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest } from '../types.js';
|
8
|
+
import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest, Worker } from '../types.js';
|
10
9
|
import { emitShutdownMessage, sendShutdownMessage } from './messages.js';
|
11
10
|
|
12
11
|
const importMetaUrl = pathToFileURL(__filename).href;
|
@@ -96,6 +95,17 @@ export async function shutdownWorkers(): Promise<void> {
|
|
96
95
|
emitShutdownMessage();
|
97
96
|
}
|
98
97
|
|
98
|
+
export function gracefullyKill(worker: Worker): void {
|
99
|
+
worker.isShuttingDown = true;
|
100
|
+
const timeout = setTimeout(() => {
|
101
|
+
worker.kill();
|
102
|
+
}, 10000);
|
103
|
+
worker.on('exit', () => {
|
104
|
+
clearTimeout(timeout);
|
105
|
+
});
|
106
|
+
sendShutdownMessage(worker);
|
107
|
+
}
|
108
|
+
|
99
109
|
export async function getCreeveyCache(): Promise<string | undefined> {
|
100
110
|
const { default: findCacheDir } = await import('find-cache-dir');
|
101
111
|
return findCacheDir({ name: 'creevey', cwd: dirname(fileURLToPath(importMetaUrl)) });
|
@@ -175,10 +185,10 @@ export function readDirRecursive(dirPath: string): string[] {
|
|
175
185
|
);
|
176
186
|
}
|
177
187
|
|
178
|
-
const _require = createRequire(importMetaUrl);
|
179
188
|
export function tryToLoadTestsData(filename: string): Partial<Record<string, ServerTest>> | undefined {
|
180
189
|
try {
|
181
|
-
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
191
|
+
return require(filename) as Partial<Record<string, ServerTest>>;
|
182
192
|
} catch {
|
183
193
|
/* noop */
|
184
194
|
}
|