creevey 0.10.0-beta.44 → 0.10.0-beta.46

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.
Files changed (36) hide show
  1. package/dist/server/index.js +2 -2
  2. package/dist/server/index.js.map +1 -1
  3. package/dist/server/master/api.d.ts +5 -10
  4. package/dist/server/master/api.js +19 -18
  5. package/dist/server/master/api.js.map +1 -1
  6. package/dist/server/master/handlers/capture-handler.d.ts +5 -2
  7. package/dist/server/master/handlers/capture-handler.js +6 -16
  8. package/dist/server/master/handlers/capture-handler.js.map +1 -1
  9. package/dist/server/master/handlers/ping-handler.d.ts +2 -2
  10. package/dist/server/master/handlers/ping-handler.js +2 -1
  11. package/dist/server/master/handlers/ping-handler.js.map +1 -1
  12. package/dist/server/master/handlers/static-handler.d.ts +1 -2
  13. package/dist/server/master/handlers/static-handler.js +10 -20
  14. package/dist/server/master/handlers/static-handler.js.map +1 -1
  15. package/dist/server/master/handlers/stories-handler.d.ts +4 -2
  16. package/dist/server/master/handlers/stories-handler.js +13 -27
  17. package/dist/server/master/handlers/stories-handler.js.map +1 -1
  18. package/dist/server/master/server.js +182 -70
  19. package/dist/server/master/server.js.map +1 -1
  20. package/dist/server/playwright/docker-file.js +2 -2
  21. package/dist/server/playwright/docker-file.js.map +1 -1
  22. package/dist/server/playwright/internal.js +3 -1
  23. package/dist/server/playwright/internal.js.map +1 -1
  24. package/dist/server/selenium/internal.js +3 -1
  25. package/dist/server/selenium/internal.js.map +1 -1
  26. package/package.json +3 -3
  27. package/src/server/index.ts +2 -2
  28. package/src/server/master/api.ts +24 -27
  29. package/src/server/master/handlers/capture-handler.ts +5 -24
  30. package/src/server/master/handlers/ping-handler.ts +4 -3
  31. package/src/server/master/handlers/static-handler.ts +10 -21
  32. package/src/server/master/handlers/stories-handler.ts +12 -40
  33. package/src/server/master/server.ts +194 -78
  34. package/src/server/playwright/docker-file.ts +2 -2
  35. package/src/server/playwright/internal.ts +3 -1
  36. package/src/server/selenium/internal.ts +3 -1
@@ -1,25 +1,28 @@
1
- import Runner from './runner.js';
2
- import { Request, Response, CreeveyUpdate } from '../../types.js';
1
+ import { Data, WebSocket, WebSocketServer } from 'ws';
2
+ import type { Request, Response, CreeveyUpdate } from '../../types.js';
3
+ import type { TestsManager } from './testsManager.js';
4
+ import type Runner from './runner.js';
3
5
  import { logger } from '../logger.js';
4
- import { TestsManager } from './testsManager.js';
5
- import HyperExpress from 'hyper-express';
6
6
 
7
- interface CustomWSServer {
8
- clients: Set<HyperExpress.Websocket>;
9
- publish: (message: string) => void;
7
+ function broadcast(wss: WebSocketServer, message: Response): void {
8
+ wss.clients.forEach((ws) => {
9
+ if (ws.readyState === WebSocket.OPEN) {
10
+ ws.send(JSON.stringify(message));
11
+ }
12
+ });
10
13
  }
11
14
 
12
- // Helper function for HyperExpress WebSocket broadcasting
13
- function broadcastHyperExpress(wss: CustomWSServer, message: Response): void {
14
- const serializedMessage = JSON.stringify(message);
15
- wss.publish(serializedMessage);
15
+ function send(ws: WebSocket, message: Response): void {
16
+ if (ws.readyState === WebSocket.OPEN) {
17
+ ws.send(JSON.stringify(message));
18
+ }
16
19
  }
17
20
 
18
- // The class-based implementation of CreeveyApi
21
+ // The class-based implementation of CreeveyApi for native WebSockets
19
22
  export class CreeveyApi {
20
23
  private runner: Runner | null = null;
21
24
  private testsManager: TestsManager;
22
- private wss: CustomWSServer | null = null;
25
+ private wss: WebSocketServer | null = null;
23
26
 
24
27
  constructor(testsManager: TestsManager, runner?: Runner) {
25
28
  this.testsManager = testsManager;
@@ -30,7 +33,7 @@ export class CreeveyApi {
30
33
  }
31
34
  }
32
35
 
33
- subscribe(wss: CustomWSServer): void {
36
+ subscribe(wss: WebSocketServer): void {
34
37
  this.wss = wss;
35
38
 
36
39
  // If we have a runner, subscribe to its updates
@@ -46,26 +49,20 @@ export class CreeveyApi {
46
49
  }
47
50
  }
48
51
 
49
- handleMessage(ws: HyperExpress.Websocket, message: string | Buffer): void {
52
+ handleMessage(ws: WebSocket, message: Data): void {
50
53
  if (typeof message != 'string') {
51
- if (Buffer.isBuffer(message)) {
52
- message = message.toString('utf-8');
53
- } else {
54
- logger().info('unhandled message', message);
55
- return;
56
- }
54
+ logger().info('unhandled message', message);
55
+ return;
57
56
  }
58
57
 
59
58
  const command = JSON.parse(message) as Request;
60
- const sendResponse = (response: Response) => {
61
- ws.send(JSON.stringify(response));
62
- };
63
59
 
64
60
  if (this.runner) {
65
61
  // Normal mode handling with runner
66
62
  switch (command.type) {
67
63
  case 'status': {
68
- sendResponse({ type: 'status', payload: this.runner.status });
64
+ const status = this.runner.status;
65
+ send(ws, { type: 'status', payload: status });
69
66
  return;
70
67
  }
71
68
  case 'start': {
@@ -98,7 +95,7 @@ export class CreeveyApi {
98
95
  }
99
96
  case 'status': {
100
97
  // In update mode, respond with static status including tests data
101
- sendResponse({
98
+ send(ws, {
102
99
  type: 'status',
103
100
  payload: {
104
101
  isRunning: false,
@@ -123,6 +120,6 @@ export class CreeveyApi {
123
120
 
124
121
  const message: Response = { type: 'update', payload };
125
122
 
126
- broadcastHyperExpress(this.wss, message);
123
+ broadcast(this.wss, message);
127
124
  }
128
125
  }
@@ -1,39 +1,20 @@
1
- import { Request, Response } from 'hyper-express';
2
1
  import cluster from 'cluster';
3
2
  import { subscribeOnWorker, sendStoriesMessage } from '../../messages.js';
4
3
  import { CaptureOptions, isDefined } from '../../../types.js';
5
4
 
6
- export async function captureHandler(request: Request, response: Response): Promise<void> {
7
- const { workerId, options } = await request.json<
8
- { workerId: number; options?: CaptureOptions },
9
- {
10
- workerId: number;
11
- options?: CaptureOptions;
12
- }
13
- >({
14
- workerId: 0,
15
- options: undefined,
16
- });
17
-
5
+ export function captureHandler({ workerId, options }: { workerId: number; options?: CaptureOptions }): void {
18
6
  const worker = Object.values(cluster.workers ?? {})
19
7
  .filter(isDefined)
20
8
  .find((worker) => worker.process.pid == workerId);
21
9
 
22
10
  // NOTE: Hypothetical case when someone send to us capture req and we don't have a worker with browser session for it
23
11
  if (!worker) {
24
- response.send();
25
12
  return;
26
13
  }
27
14
 
28
- await new Promise<void>((resolve) => {
29
- const unsubscribe = subscribeOnWorker(worker, 'stories', (message) => {
30
- if (message.type != 'capture') return;
31
- unsubscribe();
32
- resolve();
33
- });
34
- sendStoriesMessage(worker, { type: 'capture', payload: options });
15
+ const unsubscribe = subscribeOnWorker(worker, 'stories', (message) => {
16
+ if (message.type != 'capture') return;
17
+ unsubscribe();
35
18
  });
36
-
37
- // TODO Pass screenshot result to show it in inspector
38
- response.send('Ok');
19
+ sendStoriesMessage(worker, { type: 'capture', payload: options });
39
20
  }
@@ -1,5 +1,6 @@
1
- import { Request, Response } from 'hyper-express';
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
2
 
3
- export function pingHandler(_request: Request, response: Response): void {
4
- response.send('pong');
3
+ export function pingHandler(_request: IncomingMessage, response: ServerResponse): void {
4
+ response.setHeader('Content-Type', 'text/plain');
5
+ response.end('pong');
5
6
  }
@@ -1,29 +1,18 @@
1
- import { Request, Response } from 'hyper-express';
2
1
  import path from 'path';
3
2
  import fs from 'fs';
4
- import { logger } from '../../logger.js';
5
3
 
6
- export function createStaticFileHandler(baseDir: string, pathPrefix?: string) {
7
- return (request: Request, response: Response): void => {
8
- try {
9
- const decodedPath = decodeURIComponent(request.path);
10
- const relativePath = pathPrefix ? decodedPath.replace(pathPrefix, '') : decodedPath;
11
- let filePath = path.join(baseDir, relativePath || 'index.html');
4
+ export function staticHandler(baseDir: string, pathPrefix?: string) {
5
+ return (requestedPath: string): string | undefined => {
6
+ const relativePath = pathPrefix ? requestedPath.replace(pathPrefix, '') : requestedPath;
7
+ let filePath = path.join(baseDir, relativePath || 'index.html');
12
8
 
13
- // If the path points to a directory, append index.html
14
- if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
15
- filePath = path.join(filePath, 'index.html');
16
- }
17
-
18
- if (!fs.existsSync(filePath)) {
19
- response.status(404).send('File not found');
20
- return;
21
- }
9
+ // If the path points to a directory, append index.html
10
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
11
+ filePath = path.join(filePath, 'index.html');
12
+ }
22
13
 
23
- response.sendFile(filePath);
24
- } catch (error) {
25
- logger().error('Error serving file', error);
26
- response.status(500).send('Internal server error');
14
+ if (!fs.existsSync(filePath)) {
15
+ return undefined;
27
16
  }
28
17
  };
29
18
  }
@@ -1,48 +1,20 @@
1
- import { Request, Response } from 'hyper-express';
2
1
  import cluster from 'cluster';
3
2
  import { emitStoriesMessage, sendStoriesMessage } from '../../messages.js';
4
3
  import { isDefined, StoryInput } from '../../../types.js';
5
4
  import { deserializeStory } from '../../../shared/index.js';
6
5
 
7
- export function createStoriesHandler() {
8
- let setStoriesCounter = 0;
6
+ export function storiesHandler({ stories }: { stories: [string, StoryInput[]][] }): void {
7
+ const deserializedStories = stories.map<[string, StoryInput[]]>(([file, stories]) => [
8
+ file,
9
+ stories.map(deserializeStory),
10
+ ]);
9
11
 
10
- // TODO We need this handler for getting stories updates from a browser
11
- return async (request: Request, response: Response): Promise<void> => {
12
- const { setStoriesCounter: counter, stories } = await request.json<
13
- {
14
- setStoriesCounter: number;
15
- stories: [string, StoryInput[]][];
16
- },
17
- {
18
- setStoriesCounter: number;
19
- stories: [string, StoryInput[]][];
20
- }
21
- >({
22
- setStoriesCounter: 0,
23
- stories: [],
24
- });
25
-
26
- if (setStoriesCounter >= counter) {
27
- response.send();
28
- return;
29
- }
30
-
31
- const deserializedStories = stories.map<[string, StoryInput[]]>(([file, stories]) => [
32
- file,
33
- stories.map(deserializeStory),
34
- ]);
12
+ emitStoriesMessage({ type: 'update', payload: deserializedStories });
35
13
 
36
- setStoriesCounter = counter;
37
- emitStoriesMessage({ type: 'update', payload: deserializedStories });
38
-
39
- Object.values(cluster.workers ?? {})
40
- .filter(isDefined)
41
- .filter((worker) => worker.isConnected())
42
- .forEach((worker) => {
43
- sendStoriesMessage(worker, { type: 'update', payload: deserializedStories });
44
- });
45
-
46
- response.send();
47
- };
14
+ Object.values(cluster.workers ?? {})
15
+ .filter(isDefined)
16
+ .filter((worker) => worker.isConnected())
17
+ .forEach((worker) => {
18
+ sendStoriesMessage(worker, { type: 'update', payload: deserializedStories });
19
+ });
48
20
  }
@@ -1,121 +1,237 @@
1
+ import fs from 'fs';
2
+ import url from 'url';
1
3
  import path from 'path';
2
- import HyperExpress from 'hyper-express';
4
+ import { IncomingMessage, ServerResponse, createServer } from 'http';
5
+ import { WebSocketServer, WebSocket, RawData } from 'ws';
3
6
  import { fileURLToPath, pathToFileURL } from 'url';
4
- import { CreeveyApi } from './api.js';
7
+ import { shutdownOnException } from '../utils.js';
5
8
  import { subscribeOn } from '../messages.js';
6
9
  import { noop } from '../../types.js';
7
10
  import { logger } from '../logger.js';
8
- import { pingHandler, createStoriesHandler, captureHandler, createStaticFileHandler } from './handlers/index.js';
11
+ import { CreeveyApi } from './api.js';
12
+ import { pingHandler, captureHandler, storiesHandler, staticHandler } from './handlers/index.js';
9
13
 
10
- const importMetaUrl = pathToFileURL(__filename).href;
14
+ function json<T = unknown>(
15
+ handler: (data: T) => void,
16
+ defaultValue: T,
17
+ ): (request: IncomingMessage, response: ServerResponse) => void {
18
+ return (request: IncomingMessage, response: ServerResponse) => {
19
+ const chunks: Buffer[] = [];
11
20
 
12
- export function start(reportDir: string, port: number, ui: boolean): (api: CreeveyApi) => void {
13
- let resolveApi: (api: CreeveyApi) => void = noop;
14
- const creeveyApi = new Promise<CreeveyApi>((resolve) => (resolveApi = resolve));
21
+ request.on('data', (chunk: Buffer) => {
22
+ chunks.push(chunk);
23
+ });
15
24
 
16
- // Create HyperExpress server instance
17
- const server = new HyperExpress.Server();
25
+ request.on('end', () => {
26
+ try {
27
+ const body = Buffer.concat(chunks);
28
+ const value = body.length === 0 ? defaultValue : (JSON.parse(body.toString('utf-8')) as T);
18
29
 
19
- // Store active WebSocket connections
20
- const activeConnections = new Set<HyperExpress.Websocket>();
30
+ handler(value);
31
+ response.end();
32
+ } catch (error) {
33
+ logger().error('Failed to parse JSON', error);
34
+ const errorMessage = error instanceof Error ? error.message : String(error);
35
+ response.statusCode = 500;
36
+ response.setHeader('Content-Type', 'text/plain');
37
+ response.end(`Failed to parse JSON: ${errorMessage}`);
38
+ }
39
+ });
21
40
 
22
- // Enable CORS for all routes
23
- server.use((request, response, next) => {
24
- response.header('Access-Control-Allow-Origin', '*');
25
- response.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
26
- response.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
41
+ request.on('error', (error) => {
42
+ logger().error('Failed to parse JSON', error);
43
+ const errorMessage = error instanceof Error ? error.message : String(error);
44
+ response.statusCode = 500;
45
+ response.setHeader('Content-Type', 'text/plain');
46
+ response.end(`Failed to parse JSON: ${errorMessage}`);
47
+ });
48
+ };
49
+ }
27
50
 
28
- if (request.method === 'OPTIONS') {
29
- return response.status(200).send();
51
+ function file(handler: (requestedPath: string) => string | undefined) {
52
+ return (request: IncomingMessage, response: ServerResponse) => {
53
+ const parsedUrl = url.parse(request.url ?? '/', true);
54
+ const requestedPath = parsedUrl.pathname ?? '/';
55
+
56
+ try {
57
+ const filePath = handler(requestedPath);
58
+ if (filePath) {
59
+ const stat = fs.statSync(filePath);
60
+ // Set appropriate MIME type
61
+ const ext = path.extname(filePath).toLowerCase();
62
+ const mimeTypes: Record<string, string> = {
63
+ '.html': 'text/html',
64
+ '.js': 'application/javascript',
65
+ '.css': 'text/css',
66
+ '.json': 'application/json',
67
+ '.png': 'image/png',
68
+ '.jpg': 'image/jpeg',
69
+ '.jpeg': 'image/jpeg',
70
+ '.gif': 'image/gif',
71
+ '.svg': 'image/svg+xml',
72
+ '.ico': 'image/x-icon',
73
+ };
74
+
75
+ const contentType = mimeTypes[ext] || 'application/octet-stream';
76
+
77
+ response.statusCode = 200;
78
+ response.setHeader('Content-Type', contentType);
79
+ response.setHeader('Content-Length', stat.size);
80
+
81
+ // Stream the file
82
+ const stream = fs.createReadStream(filePath);
83
+ stream.pipe(response);
84
+
85
+ stream.on('error', (error) => {
86
+ logger().error('Error streaming file', error);
87
+ if (!response.headersSent) {
88
+ const errorMessage = error instanceof Error ? error.message : String(error);
89
+ response.statusCode = 500;
90
+ response.setHeader('Content-Type', 'text/plain');
91
+ response.end(`Internal server error: ${errorMessage}`);
92
+ }
93
+ });
94
+ } else {
95
+ logger().error('File not found', requestedPath);
96
+ response.statusCode = 404;
97
+ response.setHeader('Content-Type', 'text/plain');
98
+ response.end('File not found');
99
+ }
100
+ } catch (error) {
101
+ logger().error('Failed to serve file', error);
102
+ const errorMessage = error instanceof Error ? error.message : String(error);
103
+ response.statusCode = 500;
104
+ response.setHeader('Content-Type', 'text/plain');
105
+ response.end(`Failed to serve file: ${errorMessage}`);
30
106
  }
107
+ };
108
+ }
31
109
 
32
- next();
33
- });
34
-
35
- // Health check endpoint
36
- server.get('/ping', pingHandler);
37
-
38
- // Stories endpoint
39
- server.post('/stories', createStoriesHandler());
40
-
41
- // Capture endpoint
42
- server.post('/capture', captureHandler);
110
+ const importMetaUrl = pathToFileURL(__filename).href;
43
111
 
44
- // Serve report files
45
- server.get('/report/*', createStaticFileHandler(reportDir, '/report/'));
112
+ export function start(reportDir: string, port: number, ui: boolean): (api: CreeveyApi) => void {
113
+ let wss: WebSocketServer | null = null;
114
+ let creeveyApi: CreeveyApi | null = null;
115
+ let resolveApi: (api: CreeveyApi) => void = noop;
46
116
 
47
- // Serve static files
48
117
  const webDir = path.join(path.dirname(fileURLToPath(importMetaUrl)), '../../client/web');
49
- server.get('/*', createStaticFileHandler(webDir));
50
-
51
- // If UI mode, wait for CreeveyApi to be resolved
52
- if (ui) {
53
- // Create a custom broadcast function that works with our connections
54
- const broadcast = (message: string) => {
55
- for (const connection of activeConnections) {
56
- connection.send(message);
118
+ const server = createServer();
119
+
120
+ const routes = [
121
+ {
122
+ path: '/ping',
123
+ method: 'GET',
124
+ handler: pingHandler,
125
+ },
126
+ {
127
+ path: '/stories',
128
+ method: 'POST',
129
+ handler: json(storiesHandler, { stories: [] }),
130
+ },
131
+ {
132
+ path: '/capture',
133
+ method: 'POST',
134
+ handler: json(captureHandler, { workerId: 0, options: undefined }),
135
+ },
136
+ {
137
+ path: '/report/',
138
+ method: 'GET',
139
+ handler: file(staticHandler(reportDir, '/report/')),
140
+ },
141
+ {
142
+ path: '/',
143
+ method: 'GET',
144
+ handler: file(staticHandler(webDir)),
145
+ },
146
+ ];
147
+
148
+ const router = (request: IncomingMessage, response: ServerResponse): void => {
149
+ const parsedUrl = url.parse(request.url ?? '/', true);
150
+ const path = parsedUrl.pathname ?? '/';
151
+ const method = request.method ?? 'GET';
152
+
153
+ try {
154
+ const route = routes.find((route) => path.startsWith(route.path) && route.method === method);
155
+ if (route) {
156
+ route.handler(request, response);
157
+ } else {
158
+ response.statusCode = 404;
159
+ response.setHeader('Content-Type', 'text/plain');
160
+ response.end('Not Found');
57
161
  }
58
- };
59
-
60
- // Create a custom WebSocket server that simulates the standard behavior
61
- const customWsServer = {
62
- clients: activeConnections,
63
- publish: broadcast,
64
- };
162
+ } catch (error) {
163
+ logger().error('Request handling error', error);
164
+ response.statusCode = 500;
165
+ response.setHeader('Content-Type', 'text/plain');
166
+ response.end('Internal Server Error');
167
+ }
168
+ };
65
169
 
66
- let api: CreeveyApi | null = null;
170
+ server.on('request', (request: IncomingMessage, response: ServerResponse): void => {
171
+ response.setHeader('Access-Control-Allow-Origin', '*');
172
+ response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
173
+ response.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
67
174
 
68
- server.use(async (request, _response, next) => {
69
- if (!api && request.path === '/') {
70
- api = await creeveyApi;
71
- api.subscribe(customWsServer);
72
- }
73
- next();
74
- });
175
+ if (request.method === 'OPTIONS') {
176
+ response.statusCode = 200;
177
+ response.end();
178
+ return;
179
+ }
75
180
 
76
- // Create WebSocket listener
77
- server.ws('/', (ws) => {
78
- // Add connection to the set of active connections
79
- activeConnections.add(ws);
181
+ router(request, response);
182
+ });
80
183
 
81
- // Handle message events
82
- ws.on('message', (message: string | Buffer) => {
83
- api?.handleMessage(ws, message);
184
+ if (ui) {
185
+ wss = new WebSocketServer({ server });
186
+ wss.on('connection', (ws: WebSocket) => {
187
+ ws.on('message', (message: RawData, isBinary: boolean) => {
188
+ if (creeveyApi) {
189
+ // NOTE Text messages are passed as Buffer https://github.com/websockets/ws/releases/tag/8.0.0
190
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
191
+ creeveyApi.handleMessage(ws, isBinary ? message : message.toString('utf-8'));
192
+ return;
193
+ }
84
194
  });
85
195
 
86
- // Handle close events to clean up connections
87
- ws.on('close', () => {
88
- activeConnections.delete(ws);
196
+ ws.on('error', (error) => {
197
+ logger().error('WebSocket error', error);
89
198
  });
90
199
  });
200
+
201
+ wss.on('error', (error) => {
202
+ logger().error('WebSocket error', error);
203
+ });
91
204
  }
92
205
 
93
- // Shutdown handling
94
206
  subscribeOn('shutdown', () => {
95
- // Close all WebSocket connections
96
- for (const connection of activeConnections) {
97
- try {
98
- connection.close();
99
- } catch (error) {
100
- logger().error('Error closing WebSocket connection', error);
101
- }
207
+ if (wss) {
208
+ wss.clients.forEach((ws) => {
209
+ ws.close();
210
+ });
211
+ wss.close();
102
212
  }
103
213
 
104
- // Close the server
105
214
  server.close();
106
215
  });
107
216
 
108
- // Start server
109
217
  server
110
- .listen(port)
111
- .then(() => {
218
+ .listen(port, () => {
112
219
  logger().info(`Server starting on port ${port}`);
113
220
  })
114
- .catch((error: unknown) => {
221
+ .on('error', (error: unknown) => {
115
222
  logger().error('Failed to start server', error);
116
223
  process.exit(1);
117
224
  });
118
225
 
226
+ void new Promise<CreeveyApi>((resolve) => (resolveApi = resolve))
227
+ .then((api) => {
228
+ creeveyApi = api;
229
+ if (wss) {
230
+ creeveyApi.subscribe(wss);
231
+ }
232
+ })
233
+ .catch(shutdownOnException);
234
+
119
235
  // Return the function to resolve the API
120
236
  return resolveApi;
121
237
  }
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'fs/promises';
2
2
  import { pathToFileURL } from 'url';
3
3
  import semver from 'semver';
4
- import { exec } from 'shelljs';
4
+ import sh from 'shelljs';
5
5
 
6
6
  const importMetaUrl = pathToFileURL(__filename).href;
7
7
 
@@ -11,7 +11,7 @@ export async function playwrightDockerFile(browser: string, version: string): Pr
11
11
 
12
12
  let npmRegistry;
13
13
  try {
14
- npmRegistry = exec('npm config get registry', { silent: true }).stdout.trim();
14
+ npmRegistry = sh.exec('npm config get registry', { silent: true }).stdout.trim();
15
15
  } catch {
16
16
  /* noop */
17
17
  }
@@ -439,7 +439,9 @@ export class InternalBrowser {
439
439
  }
440
440
 
441
441
  private async resolveCreeveyHost(): Promise<void> {
442
- const addresses = getAddresses();
442
+ const storybookUrl = this.#page.url();
443
+ const storybookHost = new URL(storybookUrl).hostname;
444
+ const addresses = [storybookHost, ...getAddresses()];
443
445
 
444
446
  this.#serverHost = await this.#page.evaluate(
445
447
  ([hosts, port]) => {
@@ -591,7 +591,9 @@ export class InternalBrowser {
591
591
  }
592
592
 
593
593
  private async resolveCreeveyHost(): Promise<void> {
594
- const addresses = getAddresses();
594
+ const storybookUrl = await this.#browser.getCurrentUrl();
595
+ const storybookHost = new URL(storybookUrl).hostname;
596
+ const addresses = [storybookHost, ...getAddresses()];
595
597
 
596
598
  this.#serverHost = await this.#browser.executeAsyncScript(
597
599
  function (hosts: string[], port: number, callback: (host?: string | null) => void) {