Skip to content

Commit e448aec

Browse files
Merge pull request #34121 from storybookjs/copilot/fix-storybook-init-sudo-requirement
Addon-Vitest: Make Playwright `--with-deps` platform-aware to avoid `sudo` prompt on Linux
2 parents a61002d + bfb242e commit e448aec

File tree

3 files changed

+151
-22
lines changed

3 files changed

+151
-22
lines changed

code/addons/vitest/src/postinstall.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { existsSync } from 'node:fs';
22
import * as fs from 'node:fs/promises';
33
import { writeFile } from 'node:fs/promises';
4+
import os from 'node:os';
45

56
import { babelParse, generate, traverse } from 'storybook/internal/babel';
67
import { AddonVitestService } from 'storybook/internal/cli';
@@ -165,9 +166,17 @@ export default async function postInstall(options: PostinstallOptions) {
165166
useRemotePkg: !!options.skipInstall,
166167
});
167168
} else {
169+
const platform = os.platform();
170+
const useWithDeps = platform === 'darwin' || platform === 'win32';
171+
const manualCommand = useWithDeps
172+
? 'npx playwright install chromium --with-deps'
173+
: 'npx playwright install chromium';
174+
const linuxNote = !useWithDeps
175+
? '\n Note: add --with-deps to the command above if you are on Debian or Ubuntu.'
176+
: '';
168177
logger.warn(dedent`
169178
Playwright browser binaries installation skipped. Please run the following command manually later:
170-
${CLI_COLORS.cta('npx playwright install chromium --with-deps')}
179+
${CLI_COLORS.cta(manualCommand)}${linuxNote}
171180
`);
172181
}
173182
}

code/core/src/cli/AddonVitestService.test.ts

Lines changed: 123 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as fs from 'node:fs/promises';
2+
import os from 'node:os';
23

34
import { beforeEach, describe, expect, it, vi } from 'vitest';
45

@@ -14,6 +15,7 @@ import { SupportedBuilder, SupportedFramework } from '../types';
1415
import { AddonVitestService } from './AddonVitestService';
1516

1617
vi.mock('node:fs/promises', { spy: true });
18+
vi.mock('node:os', { spy: true });
1719
vi.mock('storybook/internal/common', { spy: true });
1820
vi.mock('storybook/internal/node-logger', { spy: true });
1921
vi.mock('empathic/find', { spy: true });
@@ -391,7 +393,7 @@ describe('AddonVitestService', () => {
391393
vi.mocked(logger.warn).mockImplementation(() => {});
392394
// Mock getPackageCommand to return a string
393395
vi.mocked(mockPackageManager.getPackageCommand).mockReturnValue(
394-
'npx playwright install chromium --with-deps'
396+
'npx playwright install chromium'
395397
);
396398
});
397399

@@ -416,25 +418,127 @@ describe('AddonVitestService', () => {
416418
});
417419

418420
it('should execute playwright install command', async () => {
419-
type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
420-
let commandFactory: ChildProcessFactory | ChildProcessFactory[];
421-
vi.mocked(prompt.confirm).mockResolvedValue(true);
422-
vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
423-
async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
424-
commandFactory = Array.isArray(factory) ? factory[0] : factory;
425-
// Simulate the child process completion
426-
commandFactory();
421+
const originalCI = process.env.CI;
422+
delete process.env.CI;
423+
vi.mocked(os.platform).mockReturnValue('linux');
424+
try {
425+
type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
426+
let commandFactory: ChildProcessFactory | ChildProcessFactory[];
427+
vi.mocked(prompt.confirm).mockResolvedValue(true);
428+
vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
429+
async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
430+
commandFactory = Array.isArray(factory) ? factory[0] : factory;
431+
// Simulate the child process completion
432+
commandFactory();
433+
}
434+
);
435+
436+
await service.installPlaywright();
437+
438+
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({
439+
args: ['playwright', 'install', 'chromium'],
440+
signal: undefined,
441+
stdio: ['inherit', 'pipe', 'pipe'],
442+
});
443+
} finally {
444+
if (originalCI !== undefined) {
445+
process.env.CI = originalCI;
427446
}
428-
);
429-
430-
await service.installPlaywright();
431-
432-
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({
433-
args: ['playwright', 'install', 'chromium', '--with-deps'],
434-
signal: undefined,
435-
stdio: ['inherit', 'pipe', 'pipe'],
436-
});
437-
});
447+
}
448+
});
449+
450+
it('should warn about missing system dependencies after install on Linux', async () => {
451+
const originalCI = process.env.CI;
452+
delete process.env.CI;
453+
vi.mocked(os.platform).mockReturnValue('linux');
454+
try {
455+
type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
456+
vi.mocked(prompt.confirm).mockResolvedValue(true);
457+
vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
458+
async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
459+
const commandFactory = Array.isArray(factory) ? factory[0] : factory;
460+
commandFactory();
461+
}
462+
);
463+
464+
const { result } = await service.installPlaywright();
465+
466+
expect(result).toBe('installed');
467+
expect(logger.warn).toHaveBeenCalledWith(
468+
expect.stringContaining('installed without system dependencies')
469+
);
470+
expect(logger.warn).toHaveBeenCalledWith(
471+
expect.stringContaining('run Storybook Test from the Storybook UI')
472+
);
473+
} finally {
474+
if (originalCI !== undefined) {
475+
process.env.CI = originalCI;
476+
}
477+
}
478+
});
479+
480+
it('should execute playwright install command with --with-deps in CI', async () => {
481+
const originalCI = process.env.CI;
482+
process.env.CI = 'true';
483+
vi.mocked(os.platform).mockReturnValue('linux');
484+
try {
485+
type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
486+
let commandFactory: ChildProcessFactory | ChildProcessFactory[];
487+
vi.mocked(prompt.confirm).mockResolvedValue(true);
488+
vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
489+
async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
490+
commandFactory = Array.isArray(factory) ? factory[0] : factory;
491+
commandFactory();
492+
}
493+
);
494+
495+
await service.installPlaywright();
496+
497+
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({
498+
args: ['playwright', 'install', 'chromium', '--with-deps'],
499+
signal: undefined,
500+
stdio: ['inherit', 'pipe', 'pipe'],
501+
});
502+
} finally {
503+
if (originalCI === undefined) {
504+
delete process.env.CI;
505+
} else {
506+
process.env.CI = originalCI;
507+
}
508+
}
509+
});
510+
511+
it.each(['darwin', 'win32'] as const)(
512+
'should execute playwright install command with --with-deps on %s',
513+
async (platform) => {
514+
const originalCI = process.env.CI;
515+
delete process.env.CI;
516+
vi.mocked(os.platform).mockReturnValue(platform);
517+
try {
518+
type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
519+
let commandFactory: ChildProcessFactory | ChildProcessFactory[];
520+
vi.mocked(prompt.confirm).mockResolvedValue(true);
521+
vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
522+
async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
523+
commandFactory = Array.isArray(factory) ? factory[0] : factory;
524+
commandFactory();
525+
}
526+
);
527+
528+
await service.installPlaywright();
529+
530+
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({
531+
args: ['playwright', 'install', 'chromium', '--with-deps'],
532+
signal: undefined,
533+
stdio: ['inherit', 'pipe', 'pipe'],
534+
});
535+
} finally {
536+
if (originalCI !== undefined) {
537+
process.env.CI = originalCI;
538+
}
539+
}
540+
}
541+
);
438542

439543
it('should capture error stack when installation fails', async () => {
440544
const error = new Error('Installation failed');

code/core/src/cli/AddonVitestService.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'node:fs/promises';
2+
import os from 'node:os';
23

34
import * as babel from 'storybook/internal/babel';
45
import type { JsPackageManager } from 'storybook/internal/common';
@@ -106,7 +107,10 @@ export class AddonVitestService {
106107
/**
107108
* Install Playwright browser binaries for @storybook/addon-vitest
108109
*
109-
* Installs Chromium with dependencies via `npx playwright install chromium --with-deps`
110+
* Installs Chromium via `npx playwright install chromium`. In CI environments and on
111+
* macOS/Windows (officially supported platforms), also installs system-level browser dependencies
112+
* via `--with-deps`. On other platforms (e.g. Linux), `--with-deps` is omitted to avoid requiring
113+
* `sudo` — system packages are typically managed by the distro package manager.
110114
*
111115
* @param packageManager - The package manager to use for installation
112116
* @param prompt - The prompt instance for displaying progress
@@ -123,7 +127,11 @@ export class AddonVitestService {
123127
): Promise<{ errors: string[]; result: 'installed' | 'skipped' | 'aborted' | 'failed' }> {
124128
const errors: string[] = [];
125129

126-
const playwrightCommand = ['playwright', 'install', 'chromium', '--with-deps'];
130+
const platform = os.platform();
131+
const useWithDeps = !!process.env.CI || platform === 'darwin' || platform === 'win32';
132+
const playwrightCommand = useWithDeps
133+
? ['playwright', 'install', 'chromium', '--with-deps']
134+
: ['playwright', 'install', 'chromium'];
127135
const playwrightCommandString = this.packageManager.getPackageCommand(playwrightCommand);
128136

129137
let result: 'installed' | 'skipped' | 'aborted' | 'failed';
@@ -168,6 +176,14 @@ export class AddonVitestService {
168176
result = 'aborted';
169177
} else {
170178
result = 'installed';
179+
if (!useWithDeps) {
180+
logger.warn(dedent`
181+
Playwright was installed without system dependencies. Depending on your operating system, you may need to install additional libraries for Playwright to work correctly.
182+
To check for missing dependencies, run Storybook Test from the Storybook UI — it will report any libraries that need to be installed.
183+
On MacOS, Windows, Debian and Ubuntu, you can install system dependencies manually by running:
184+
${CLI_COLORS.cta(this.packageManager.getPackageCommand(['playwright', 'install', 'chromium', '--with-deps']))}
185+
`);
186+
}
171187
}
172188
} else {
173189
logger.warn('Playwright installation skipped');

0 commit comments

Comments
 (0)