creevey 0.10.0-beta.3 → 0.10.0-beta.31
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/Addon.js +17 -7
- package/dist/client/addon/components/Addon.js.map +1 -1
- package/dist/client/addon/components/Panel.js +2 -2
- package/dist/client/addon/components/Panel.js.map +1 -1
- package/dist/client/addon/components/Tools.js +17 -7
- package/dist/client/addon/components/Tools.js.map +1 -1
- package/dist/client/addon/withCreevey.d.ts +1 -0
- package/dist/client/addon/withCreevey.js +10 -1
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/BlendView.js +17 -7
- package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SideBySideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SlideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.js +29 -7
- 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/PageHeader/PageHeader.js +20 -8
- package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.js +43 -13
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/shared/creeveyClientApi.js +8 -1
- package/dist/client/shared/creeveyClientApi.js.map +1 -1
- package/dist/client/shared/helpers.d.ts +1 -3
- package/dist/client/shared/helpers.js +4 -19
- package/dist/client/shared/helpers.js.map +1 -1
- package/dist/client/web/CreeveyApp.js +41 -14
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +5 -0
- package/dist/client/web/CreeveyContext.js +20 -7
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyLoader.js +2 -2
- package/dist/client/web/CreeveyLoader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +19 -9
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +18 -7
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +60 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +17 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +18 -10
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +18 -10
- 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 +79 -64
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/index-Cs8IUTQs.js +595 -0
- package/dist/client/web/index.html +1 -1
- package/dist/client/web/index.js +17 -7
- package/dist/client/web/index.js.map +1 -1
- package/dist/client/web/themes.d.ts +2 -0
- package/dist/client/web/themes.js +22 -0
- package/dist/client/web/themes.js.map +1 -0
- package/dist/creevey.js +16 -9
- package/dist/creevey.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/server/config.d.ts +1 -1
- package/dist/server/config.js +29 -7
- package/dist/server/config.js.map +1 -1
- package/dist/server/connection.d.ts +3 -0
- package/dist/server/connection.js +28 -0
- package/dist/server/connection.js.map +1 -0
- package/dist/server/docker.js +38 -21
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +63 -11
- 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 +4 -3
- package/dist/server/master/pool.js +12 -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 +71 -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 +13 -11
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/docker-file.d.ts +2 -1
- package/dist/server/playwright/docker-file.js +7 -5
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/internal.d.ts +5 -4
- package/dist/server/playwright/internal.js +91 -71
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.d.ts +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 +6 -4
- 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 +13 -9
- package/dist/server/reporter.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +3 -4
- package/dist/server/selenium/internal.js +127 -99
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +9 -6
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.d.ts +1 -1
- package/dist/server/selenium/webdriver.js +1 -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/testsFiles/parser.js +44 -2
- package/dist/server/testsFiles/parser.js.map +1 -1
- package/dist/server/utils.d.ts +20 -1
- package/dist/server/utils.js +82 -7
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +3 -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/match-image.d.ts +4 -4
- package/dist/server/worker/match-image.js +7 -4
- package/dist/server/worker/match-image.js.map +1 -1
- package/dist/server/worker/start.js +24 -14
- package/dist/server/worker/start.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/types.d.ts +38 -13
- package/dist/types.js.map +1 -1
- package/docs/config.md +3 -0
- package/package.json +65 -63
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/withCreevey.ts +8 -1
- 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/PageHeader/PageHeader.tsx +4 -2
- package/src/client/shared/components/ResultsPage.tsx +31 -8
- package/src/client/shared/creeveyClientApi.ts +9 -1
- package/src/client/shared/helpers.ts +4 -24
- package/src/client/web/CreeveyApp.tsx +26 -8
- package/src/client/web/CreeveyContext.tsx +9 -0
- package/src/client/web/CreeveyLoader.tsx +1 -1
- 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/client/web/themes.ts +24 -0
- package/src/creevey.ts +16 -10
- package/src/server/config.ts +30 -8
- package/src/server/connection.ts +26 -0
- package/src/server/docker.ts +42 -24
- package/src/server/index.ts +73 -14
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +22 -56
- package/src/server/master/queue.ts +77 -0
- package/src/server/master/runner.ts +4 -1
- package/src/server/master/server.ts +1 -1
- package/src/server/master/start.ts +16 -11
- package/src/server/playwright/docker-file.ts +8 -5
- package/src/server/playwright/internal.ts +91 -78
- package/src/server/playwright/webdriver.ts +2 -2
- package/src/server/providers/browser.ts +6 -4
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporter.ts +15 -9
- package/src/server/selenium/internal.ts +131 -107
- package/src/server/selenium/selenoid.ts +9 -7
- package/src/server/selenium/webdriver.ts +2 -2
- package/src/server/telemetry.ts +7 -3
- package/src/server/testsFiles/parser.ts +51 -1
- package/src/server/utils.ts +87 -8
- package/src/server/webdriver.ts +11 -16
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/match-image.ts +12 -8
- package/src/server/worker/start.ts +25 -16
- package/src/shared/index.ts +1 -1
- package/src/types.ts +40 -15
- package/types/global.d.ts +1 -0
- package/.yarnrc.yml +0 -1
- package/chromatic.config.json +0 -5
- package/dist/client/web/assets/index-DkmZfG9C.js +0 -591
package/src/server/reporter.ts
CHANGED
@@ -22,23 +22,24 @@ 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.
|
34
|
+
testLogger.warn(chalk.cyan(test.fullTitle()));
|
35
35
|
});
|
36
36
|
runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
|
37
|
-
testLogger.info(chalk.cyan(test.
|
37
|
+
testLogger.info(chalk.cyan(test.fullTitle()), chalk.gray(`(${test.duration} ms)`));
|
38
38
|
});
|
39
39
|
runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error) => {
|
40
40
|
testLogger.error(
|
41
|
-
chalk.cyan(test.
|
41
|
+
chalk.cyan(test.fullTitle()),
|
42
|
+
chalk.gray(`(${test.duration} ms)`),
|
42
43
|
'\n ',
|
43
44
|
this.getErrors(
|
44
45
|
error,
|
@@ -76,7 +77,11 @@ export class TeamcityReporter {
|
|
76
77
|
const reporterOptions = options.reporterOptions.creevey;
|
77
78
|
|
78
79
|
runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
|
79
|
-
console.log(`##teamcity[testStarted name='${this.escape(test.
|
80
|
+
console.log(`##teamcity[testStarted name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
|
81
|
+
});
|
82
|
+
|
83
|
+
runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
|
84
|
+
console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
|
80
85
|
});
|
81
86
|
|
82
87
|
runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error: Error) => {
|
@@ -84,6 +89,7 @@ export class TeamcityReporter {
|
|
84
89
|
if (!image) return;
|
85
90
|
const filePath = test
|
86
91
|
.titlePath()
|
92
|
+
.slice(0, -1)
|
87
93
|
.concat(name == browserName ? [] : [browserName])
|
88
94
|
.map(this.escape)
|
89
95
|
.join('/');
|
@@ -97,7 +103,7 @@ export class TeamcityReporter {
|
|
97
103
|
);
|
98
104
|
console.log(
|
99
105
|
`##teamcity[testMetadata testName='${this.escape(
|
100
|
-
test.
|
106
|
+
test.fullTitle(),
|
101
107
|
)}' type='image' value='report/${filePath}/${fileName}' flowId='${process.pid}']`,
|
102
108
|
);
|
103
109
|
});
|
@@ -107,10 +113,10 @@ export class TeamcityReporter {
|
|
107
113
|
// https://teamcity-support.jetbrains.com/hc/en-us/community/posts/207216829-Count-test-as-successful-if-at-least-one-try-is-successful?page=1#community_comment_207394125
|
108
114
|
|
109
115
|
if (reporterOptions.willRetry)
|
110
|
-
console.log(`##teamcity[testFinished name='${this.escape(test.
|
116
|
+
console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
|
111
117
|
else
|
112
118
|
console.log(
|
113
|
-
`##teamcity[testFailed name='${this.escape(test.
|
119
|
+
`##teamcity[testFailed name='${this.escape(test.fullTitle())}' message='${this.escape(
|
114
120
|
error.message,
|
115
121
|
)}' details='${this.escape(error.stack ?? '')}' flowId='${process.pid}']`,
|
116
122
|
);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Args } from '@storybook/csf';
|
1
|
+
import type { Args } from '@storybook/csf';
|
2
2
|
import chalk from 'chalk';
|
3
3
|
import http from 'http';
|
4
4
|
import https from 'https';
|
@@ -24,7 +24,7 @@ import {
|
|
24
24
|
StorybookEvents,
|
25
25
|
} from '../../types.js';
|
26
26
|
import { colors, logger } from '../logger.js';
|
27
|
-
import { subscribeOn } from '../messages.js';
|
27
|
+
import { emitWorkerMessage, subscribeOn } from '../messages.js';
|
28
28
|
import { getTestPath, isShuttingDown, runSequence } from '../utils.js';
|
29
29
|
import {
|
30
30
|
appendIframePath,
|
@@ -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 {
|
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,13 @@ export class InternalBrowser {
|
|
159
191
|
#browser: WebDriver;
|
160
192
|
#serverHost: string | null = null;
|
161
193
|
#serverPort: number;
|
162
|
-
#
|
194
|
+
#storybookGlobals?: StorybookGlobals;
|
163
195
|
#unsubscribe: () => void = noop;
|
164
196
|
#keepAliveInterval: NodeJS.Timeout | null = null;
|
165
|
-
constructor(browser: WebDriver, port: number,
|
197
|
+
constructor(browser: WebDriver, port: number, storybookGlobals?: StorybookGlobals) {
|
166
198
|
this.#browser = browser;
|
167
199
|
this.#serverPort = port;
|
168
|
-
this.#
|
200
|
+
this.#storybookGlobals = storybookGlobals;
|
169
201
|
this.#unsubscribe = subscribeOn('shutdown', () => {
|
170
202
|
void this.closeBrowser();
|
171
203
|
});
|
@@ -194,7 +226,7 @@ export class InternalBrowser {
|
|
194
226
|
|
195
227
|
const ignoreStyles = await this.insertIgnoreStyles(ignoreElements);
|
196
228
|
|
197
|
-
if (
|
229
|
+
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
198
230
|
const { innerWidth, innerHeight } = await this.#browser.executeScript<{
|
199
231
|
innerWidth: number;
|
200
232
|
innerHeight: number;
|
@@ -204,16 +236,16 @@ export class InternalBrowser {
|
|
204
236
|
innerHeight: window.innerHeight,
|
205
237
|
};
|
206
238
|
});
|
207
|
-
|
239
|
+
logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
|
208
240
|
}
|
209
241
|
|
210
242
|
try {
|
211
243
|
if (!captureElement) {
|
212
|
-
|
244
|
+
logger().debug('Capturing viewport screenshot');
|
213
245
|
screenshot = await this.#browser.takeScreenshot();
|
214
|
-
|
246
|
+
logger().debug('Viewport screenshot is captured');
|
215
247
|
} else {
|
216
|
-
|
248
|
+
logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
|
217
249
|
const rects = await this.#browser.executeScript<
|
218
250
|
{ elementRect: ElementRect; windowRect: ElementRect } | undefined
|
219
251
|
>(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
|
@@ -252,11 +284,11 @@ export class InternalBrowser {
|
|
252
284
|
elementRect.height + elementRect.top <= windowRect.height;
|
253
285
|
|
254
286
|
if (isFitIntoViewport) {
|
255
|
-
|
287
|
+
logger().debug(
|
256
288
|
`Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
257
289
|
);
|
258
290
|
} else
|
259
|
-
|
291
|
+
logger().debug(
|
260
292
|
`Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
261
293
|
);
|
262
294
|
|
@@ -272,7 +304,7 @@ export class InternalBrowser {
|
|
272
304
|
: // TODO pointer-events: none, need to research
|
273
305
|
await this.takeCompositeScreenshot(windowRect, elementRect);
|
274
306
|
|
275
|
-
|
307
|
+
logger().debug(`${chalk.cyan(captureElement)} is captured`);
|
276
308
|
}
|
277
309
|
} finally {
|
278
310
|
await this.removeIgnoreStyles(ignoreStyles);
|
@@ -291,10 +323,11 @@ export class InternalBrowser {
|
|
291
323
|
|
292
324
|
async selectStory(id: string, waitForReady = false): Promise<boolean> {
|
293
325
|
// NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
|
326
|
+
await this.updateStorybookGlobals();
|
294
327
|
await this.updateBrowserGlobalVariables();
|
295
328
|
await this.resetMousePosition();
|
296
329
|
|
297
|
-
|
330
|
+
logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
|
298
331
|
|
299
332
|
const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
|
300
333
|
function (
|
@@ -356,14 +389,14 @@ export class InternalBrowser {
|
|
356
389
|
}
|
357
390
|
|
358
391
|
async afterTest(test: ServerTest): Promise<void> {
|
359
|
-
if (
|
392
|
+
if (logger().getLevel() === Logger.levels.TRACE) {
|
360
393
|
const output: string[] = [];
|
361
394
|
const types = await this.#browser.manage().logs().getAvailableLogTypes();
|
362
395
|
for (const type of types) {
|
363
396
|
const logs = await this.#browser.manage().logs().get(type);
|
364
397
|
output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
|
365
398
|
}
|
366
|
-
|
399
|
+
logger().debug(
|
367
400
|
'----------',
|
368
401
|
getTestPath(test).join('/'),
|
369
402
|
'----------\n',
|
@@ -387,7 +420,7 @@ export class InternalBrowser {
|
|
387
420
|
|
388
421
|
if (!browser) return null;
|
389
422
|
|
390
|
-
const internalBrowser = new InternalBrowser(browser, options.port,
|
423
|
+
const internalBrowser = new InternalBrowser(browser, options.port, _storybookGlobals);
|
391
424
|
|
392
425
|
try {
|
393
426
|
if (isShuttingDown.current) return null;
|
@@ -397,19 +430,18 @@ export class InternalBrowser {
|
|
397
430
|
gridUrl,
|
398
431
|
viewport,
|
399
432
|
storybookUrl: address,
|
400
|
-
storybookGlobals: _storybookGlobals,
|
401
|
-
resolveStorybookUrl: config.resolveStorybookUrl,
|
402
433
|
});
|
403
434
|
|
404
435
|
return done ? internalBrowser : null;
|
405
436
|
} catch (originalError) {
|
406
437
|
void internalBrowser.closeBrowser();
|
407
438
|
|
408
|
-
const message =
|
409
|
-
|
439
|
+
const message =
|
440
|
+
originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string);
|
441
|
+
const error = new Error(`Can't load storybook root page: ${message}`);
|
410
442
|
if (originalError instanceof Error) error.stack = originalError.stack;
|
411
443
|
|
412
|
-
logger.error(error);
|
444
|
+
logger().error(error);
|
413
445
|
|
414
446
|
return null;
|
415
447
|
}
|
@@ -420,15 +452,11 @@ export class InternalBrowser {
|
|
420
452
|
gridUrl,
|
421
453
|
viewport,
|
422
454
|
storybookUrl,
|
423
|
-
storybookGlobals,
|
424
|
-
resolveStorybookUrl,
|
425
455
|
}: {
|
426
456
|
browserName: string;
|
427
457
|
gridUrl: string;
|
428
458
|
viewport?: { width: number; height: number };
|
429
459
|
storybookUrl: string;
|
430
|
-
storybookGlobals?: StorybookGlobals;
|
431
|
-
resolveStorybookUrl?: () => Promise<string>;
|
432
460
|
}): Promise<boolean> {
|
433
461
|
const sessionId = (await this.#browser.getSession()).getId();
|
434
462
|
let browserHost = '';
|
@@ -440,23 +468,21 @@ export class InternalBrowser {
|
|
440
468
|
/* noop */
|
441
469
|
}
|
442
470
|
|
443
|
-
|
444
|
-
|
445
|
-
prefix.apply(this.#logger, {
|
471
|
+
prefix.apply(logger(), {
|
446
472
|
format(level) {
|
447
473
|
const levelColor = colors[level.toUpperCase() as keyof typeof colors];
|
448
|
-
return `[${browserName}:${chalk.gray(
|
474
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
|
449
475
|
},
|
450
476
|
});
|
451
477
|
|
452
|
-
|
478
|
+
logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
|
453
479
|
|
454
480
|
return await runSequence(
|
455
481
|
[
|
456
|
-
() => this.#browser.manage().setTimeouts({ pageLoad:
|
457
|
-
() => this.openStorybookPage(storybookUrl
|
482
|
+
() => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
|
483
|
+
() => this.openStorybookPage(storybookUrl),
|
458
484
|
() => this.waitForStorybook(),
|
459
|
-
() => this.updateStorybookGlobals(
|
485
|
+
() => this.updateStorybookGlobals(),
|
460
486
|
() => this.resolveCreeveyHost(),
|
461
487
|
() => this.updateBrowserGlobalVariables(),
|
462
488
|
// NOTE: Selenium draws automation toolbar with some delay after webdriver initialization
|
@@ -471,26 +497,17 @@ export class InternalBrowser {
|
|
471
497
|
);
|
472
498
|
}
|
473
499
|
|
474
|
-
private async openStorybookPage(storybookUrl: string
|
500
|
+
private async openStorybookPage(storybookUrl: string): Promise<void> {
|
475
501
|
if (!LOCALHOST_REGEXP.test(storybookUrl)) {
|
476
502
|
return this.#browser.get(appendIframePath(storybookUrl));
|
477
503
|
}
|
478
504
|
|
479
505
|
try {
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
const resolvedUrl = await resolver();
|
484
|
-
|
485
|
-
this.#logger.debug(`Resolver storybook url ${resolvedUrl}`);
|
486
|
-
|
487
|
-
await this.#browser.get(appendIframePath(resolvedUrl));
|
488
|
-
} else {
|
489
|
-
// NOTE: getUrlChecker already calls `browser.get` so we don't need another one
|
490
|
-
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url), this.#logger);
|
491
|
-
}
|
506
|
+
// TODO Pageload timeout 10s
|
507
|
+
// NOTE: getUrlChecker already calls `browser.get` so we don't need another one
|
508
|
+
await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
|
492
509
|
} catch (error) {
|
493
|
-
|
510
|
+
logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
|
494
511
|
throw error;
|
495
512
|
}
|
496
513
|
}
|
@@ -498,13 +515,13 @@ export class InternalBrowser {
|
|
498
515
|
private async checkUrl(url: string): Promise<boolean> {
|
499
516
|
try {
|
500
517
|
// NOTE: Before trying a new url, reset the current one
|
501
|
-
|
518
|
+
logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
|
502
519
|
await openUrlAndWaitForPageSource(
|
503
520
|
this.#browser,
|
504
521
|
'about:blank',
|
505
522
|
(source: string) => !source.includes('<body></body>'),
|
506
523
|
);
|
507
|
-
|
524
|
+
logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
|
508
525
|
const source = await openUrlAndWaitForPageSource(
|
509
526
|
this.#browser,
|
510
527
|
url,
|
@@ -516,7 +533,7 @@ export class InternalBrowser {
|
|
516
533
|
// because other add significant delay and some of them don't work in earlier chrome versions
|
517
534
|
// Browsers always load page successful even it's failed
|
518
535
|
// So we just check `root` element
|
519
|
-
|
536
|
+
logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
|
520
537
|
return source.includes(`id="${storybookRootID}"`);
|
521
538
|
} catch {
|
522
539
|
return false;
|
@@ -524,7 +541,7 @@ export class InternalBrowser {
|
|
524
541
|
}
|
525
542
|
|
526
543
|
private async waitForStorybook(): Promise<void> {
|
527
|
-
|
544
|
+
logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
528
545
|
|
529
546
|
const isTimeout = await Promise.race([
|
530
547
|
new Promise<boolean>((resolve) => {
|
@@ -535,19 +552,15 @@ export class InternalBrowser {
|
|
535
552
|
(async () => {
|
536
553
|
let wait = true;
|
537
554
|
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
|
-
}
|
555
|
+
// TODO Research a different way to ensure storybook is initiated
|
556
|
+
wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
|
557
|
+
// TODO Maybe use
|
558
|
+
// import { global } from '@storybook/global';
|
559
|
+
// global.IS_STORYBOOK
|
560
|
+
if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
|
561
|
+
if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
|
562
|
+
return false;
|
563
|
+
}, StorybookEvents.SET_GLOBALS);
|
551
564
|
} while (wait);
|
552
565
|
return false;
|
553
566
|
})(),
|
@@ -557,13 +570,13 @@ export class InternalBrowser {
|
|
557
570
|
if (isTimeout) throw new Error('Failed to wait `setStories` event');
|
558
571
|
}
|
559
572
|
|
560
|
-
private async updateStorybookGlobals(
|
561
|
-
if (!
|
573
|
+
private async updateStorybookGlobals(): Promise<void> {
|
574
|
+
if (!this.#storybookGlobals) return;
|
562
575
|
|
563
|
-
|
576
|
+
logger().debug('Applying storybook globals');
|
564
577
|
await this.#browser.executeScript(function (globals: StorybookGlobals) {
|
565
578
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
566
|
-
},
|
579
|
+
}, this.#storybookGlobals);
|
567
580
|
}
|
568
581
|
|
569
582
|
private async resolveCreeveyHost(): Promise<void> {
|
@@ -607,6 +620,7 @@ export class InternalBrowser {
|
|
607
620
|
private async updateBrowserGlobalVariables() {
|
608
621
|
await this.#browser.executeScript(
|
609
622
|
function (workerId: number, creeveyHost: string, creeveyPort: number) {
|
623
|
+
window.__CREEVEY_ENV__ = true;
|
610
624
|
window.__CREEVEY_WORKER_ID__ = workerId;
|
611
625
|
window.__CREEVEY_SERVER_HOST__ = creeveyHost;
|
612
626
|
window.__CREEVEY_SERVER_PORT__ = creeveyPort;
|
@@ -630,7 +644,7 @@ export class InternalBrowser {
|
|
630
644
|
},
|
631
645
|
);
|
632
646
|
|
633
|
-
|
647
|
+
logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
|
634
648
|
|
635
649
|
const dWidth = windowRect.width - innerWidth;
|
636
650
|
const dHeight = windowRect.height - innerHeight;
|
@@ -644,7 +658,7 @@ export class InternalBrowser {
|
|
644
658
|
}
|
645
659
|
|
646
660
|
private async resetMousePosition(): Promise<void> {
|
647
|
-
|
661
|
+
logger().debug('Resetting mouse position to the top-left corner');
|
648
662
|
const browserName = (await this.#browser.getCapabilities()).getBrowserName();
|
649
663
|
const [browserVersion] =
|
650
664
|
(await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
|
@@ -672,8 +686,9 @@ export class InternalBrowser {
|
|
672
686
|
y: Math.ceil((-1 * height) / 2) - top,
|
673
687
|
})
|
674
688
|
.perform();
|
675
|
-
} else if (browserName == 'firefox'
|
689
|
+
} else if (browserName == 'firefox') {
|
676
690
|
// NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad:
|
691
|
+
// NOTE In recent versions (eg 128.0) moving by 0 x 0 doesn't work at all
|
677
692
|
await this.#browser.actions().move({ origin: Origin.VIEWPORT, x: 0, y: 1 }).perform();
|
678
693
|
} else {
|
679
694
|
// NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine
|
@@ -685,7 +700,7 @@ export class InternalBrowser {
|
|
685
700
|
const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
|
686
701
|
if (!ignoreSelectors.length) return null;
|
687
702
|
|
688
|
-
|
703
|
+
logger().debug('Hiding ignored elements before capturing');
|
689
704
|
|
690
705
|
return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
|
691
706
|
return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
|
@@ -766,7 +781,7 @@ export class InternalBrowser {
|
|
766
781
|
|
767
782
|
private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
|
768
783
|
if (ignoreStyles) {
|
769
|
-
|
784
|
+
logger().debug('Revert hiding ignored elements');
|
770
785
|
await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
|
771
786
|
window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
|
772
787
|
}, ignoreStyles);
|
@@ -804,9 +819,18 @@ export class InternalBrowser {
|
|
804
819
|
private keepAlive(): void {
|
805
820
|
this.#keepAliveInterval = setInterval(() => {
|
806
821
|
// NOTE Simple way to keep session alive
|
807
|
-
void this.#browser
|
808
|
-
|
809
|
-
|
822
|
+
void this.#browser
|
823
|
+
.getCurrentUrl()
|
824
|
+
.then((url) => {
|
825
|
+
logger().debug('current url', chalk.magenta(url));
|
826
|
+
})
|
827
|
+
.catch((error: unknown) => {
|
828
|
+
logger().error(error);
|
829
|
+
emitWorkerMessage({
|
830
|
+
type: 'error',
|
831
|
+
payload: { subtype: 'browser', error: 'Failed to ping browser' },
|
832
|
+
});
|
833
|
+
});
|
810
834
|
}, 10 * 1000);
|
811
835
|
}
|
812
836
|
}
|
@@ -2,9 +2,9 @@ import path from 'path';
|
|
2
2
|
import assert from 'assert';
|
3
3
|
import { lstatSync, existsSync } from 'fs';
|
4
4
|
import { mkdir, writeFile, copyFile } from 'fs/promises';
|
5
|
-
import
|
5
|
+
import { exec, chmod } from 'shelljs';
|
6
6
|
import { Config, BrowserConfigObject } from '../../types.js';
|
7
|
-
import { downloadBinary, getCreeveyCache } from '../utils.js';
|
7
|
+
import { downloadBinary, getCreeveyCache, killTree } from '../utils.js';
|
8
8
|
import { pullImages, runImage } from '../docker.js';
|
9
9
|
import { subscribeOn } from '../messages.js';
|
10
10
|
|
@@ -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
|
}) => {
|
@@ -91,12 +91,12 @@ export async function startSelenoidStandalone(config: Config, debug: boolean): P
|
|
91
91
|
|
92
92
|
// TODO Download browser webdrivers
|
93
93
|
try {
|
94
|
-
if (process.platform != 'win32')
|
94
|
+
if (process.platform != 'win32') chmod('+x', binaryPath);
|
95
95
|
} catch {
|
96
96
|
/* noop */
|
97
97
|
}
|
98
98
|
|
99
|
-
const selenoidProcess =
|
99
|
+
const selenoidProcess = exec(`${binaryPath} -conf ./browsers.json -disable-docker`, {
|
100
100
|
async: true,
|
101
101
|
cwd: selenoidConfigDir,
|
102
102
|
});
|
@@ -106,7 +106,9 @@ export async function startSelenoidStandalone(config: Config, debug: boolean): P
|
|
106
106
|
selenoidProcess.stderr?.pipe(process.stderr);
|
107
107
|
}
|
108
108
|
|
109
|
-
subscribeOn('shutdown', () =>
|
109
|
+
subscribeOn('shutdown', () => {
|
110
|
+
if (selenoidProcess.pid) void killTree(selenoidProcess.pid);
|
111
|
+
});
|
110
112
|
}
|
111
113
|
|
112
114
|
export async function startSelenoidContainer(config: Config, debug: boolean): Promise<string> {
|
@@ -117,7 +119,7 @@ export async function startSelenoidContainer(config: Config, debug: boolean): Pr
|
|
117
119
|
browsers.forEach(
|
118
120
|
({
|
119
121
|
browserName,
|
120
|
-
browserVersion = 'latest',
|
122
|
+
seleniumCapabilities: { browserVersion = 'latest' } = {},
|
121
123
|
limit: browserLimit = 1,
|
122
124
|
dockerImage = `selenoid/${browserName}:${browserVersion}`,
|
123
125
|
}) => {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/// <reference types="../../../types/selenium-context" />
|
2
|
-
import { Args } from '@storybook/csf';
|
2
|
+
import type { Args } from '@storybook/csf';
|
3
3
|
import { Config, StorybookGlobals, StoryInput, StoriesRaw, Options, ServerTest } from '../../types.js';
|
4
4
|
import { subscribeOn } from '../messages.js';
|
5
5
|
import { CreeveyWebdriverBase } from '../webdriver.js';
|
@@ -63,7 +63,7 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
|
|
63
63
|
try {
|
64
64
|
return await import('./internal.js');
|
65
65
|
} catch (error) {
|
66
|
-
logger.error(error);
|
66
|
+
logger().error(error);
|
67
67
|
return null;
|
68
68
|
}
|
69
69
|
})();
|
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
|
]),
|