Skip to content

Commit 7f859aa

Browse files
authored
feat: add additionalSearchParams API to Storybook Runner (#782)
* feat: add additionalsearchparams api to storybook runner * chore: add changeset
1 parent aadeda7 commit 7f859aa

9 files changed

Lines changed: 88 additions & 10 deletions

File tree

.changeset/early-timers-crash.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"webdriver-image-comparison": minor
3+
"@wdio/visual-service": minor
4+
---
5+
6+
Add `additionalSearchParams` to the Storybook Runner API

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ export const config: WebdriverIO.Config = {
6767

6868
### Storybook Runner CLI options
6969

70+
#### `--additionalSearchParams`
71+
72+
- **Type:** `string`
73+
- **Mandatory:** No
74+
- **Default:** ''
75+
- **Example:** `npx wdio tests/configs/wdio.local.desktop.storybook.conf.ts --storybook --additionalSearchParams="foo=bar&abc=def"`
76+
77+
It will add additional search parameters to the Storybook URL.
78+
See the [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) documentation for more information. The string must be a valid URLSearchParams string.
79+
80+
> [!NOTE]
81+
> The double quotes are needed to prevent the `&` from being interpreted as a command separator.
82+
> For example with `--additionalSearchParams="foo=bar&abc=def"` it will generate the following Storybook URL for stories test: `http://storybook.url/iframe.html?id=story-id&foo=bar&abc=def`.
83+
7084
#### `--browsers`
7185

7286
- **Type:** `string`

packages/visual-service/src/storybook/Types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface StoriesRes {
3030
export type Stories = { [key: string]: StorybookData };
3131

3232
export type CreateTestFileOptions = {
33+
additionalSearchParams: URLSearchParams;
3334
clip: boolean;
3435
clipSelector: string;
3536
directoryPath: string,
@@ -49,6 +50,7 @@ export interface CapabilityMap {
4950
}
5051

5152
export type CreateTestContent = {
53+
additionalSearchParams: URLSearchParams;
5254
clip: boolean;
5355
clipSelector: string;
5456
folders: Folders;
@@ -59,6 +61,7 @@ export type CreateTestContent = {
5961
}
6062

6163
export type CreateItContent = {
64+
additionalSearchParams: URLSearchParams;
6265
clip: boolean;
6366
clipSelector: string;
6467
folders: Folders;
@@ -83,6 +86,13 @@ export type EmulatedDeviceType = {
8386
}
8487

8588
export type WaitForStorybookComponentToBeLoaded = {
89+
/**
90+
* Additional search parameters to be added to the Storybook URL
91+
*
92+
* @example addtionalSearchParams: new URLSearchParams({ foo: 'bar', abc: 'def' })
93+
* This will generate the following Storybook URL for stories test: `http://storybook.url/iframe.html?id=story-id&foo=bar&abc=def`
94+
*/
95+
additionalSearchParams?: URLSearchParams;
8696
clipSelector?: string,
8797
id: string;
8898
timeout?: number,

packages/visual-service/src/storybook/launcher.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,14 @@ export default class VisualLauncher extends BaseClass {
7878
const skipStoriesArgv = getArgvValue('--skipStories', value => value)
7979
const skipStories = skipStoriesOption ?? skipStoriesArgv ?? []
8080
const parsedSkipStories = parseSkipStories(skipStories)
81+
// --additionalSearchParams
82+
const additionalSearchParamsOption = this.#options?.storybook?.additionalSearchParams
83+
const additionalSearchParamsArgv = getArgvValue('--additionalSearchParams', value => new URLSearchParams(value))
84+
const additionalSearchParams = additionalSearchParamsOption ?? additionalSearchParamsArgv ?? new URLSearchParams()
8185

8286
// Create the test files
8387
createTestFiles({
88+
additionalSearchParams,
8489
clip,
8590
clipSelector,
8691
directoryPath: tempDir,

packages/visual-service/src/storybook/utils.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export function getArgvValue<ParseFuncReturnType>(
149149
* Creates a it function for the test file
150150
* @TODO: improve this
151151
*/
152-
export function itFunction({ clip, clipSelector, folders: { baselineFolder }, framework, skipStories, storyData, storybookUrl }: CreateItContent) {
152+
export function itFunction({ additionalSearchParams, clip, clipSelector, folders: { baselineFolder }, framework, skipStories, storyData, storybookUrl }: CreateItContent) {
153153
const { id } = storyData
154154
const screenshotType = clip ? 'n element' : ' viewport'
155155
const DEFAULT_IT_TEXT = 'it'
@@ -179,6 +179,7 @@ export function itFunction({ clip, clipSelector, folders: { baselineFolder }, fr
179179
clipSelector: '${clipSelector}',
180180
id: '${id}',
181181
storybookUrl: '${storybookUrl}',
182+
additionalSearchParams: new URLSearchParams('${additionalSearchParams.toString()}'),
182183
});
183184
${clip
184185
? `await expect($('${clipSelector}')).toMatchElementSnapshot('${id}-element', ${JSON.stringify(methodOptions)})`
@@ -206,11 +207,11 @@ export function writeTestFile(directoryPath: string, fileID: string, testContent
206207
* Create the test content
207208
*/
208209
export function createTestContent(
209-
{ clip, clipSelector, folders, framework, skipStories, stories, storybookUrl }: CreateTestContent,
210+
{ additionalSearchParams, clip, clipSelector, folders, framework, skipStories, stories, storybookUrl }: CreateTestContent,
210211
// For testing purposes only
211212
itFunc = itFunction
212213
): string {
213-
const itFunctionOptions = { clip, clipSelector, folders, framework, skipStories, storybookUrl }
214+
const itFunctionOptions = { additionalSearchParams, clip, clipSelector, folders, framework, skipStories, storybookUrl }
214215

215216
return stories.reduce((acc, storyData) => acc + itFunc({ ...itFunctionOptions, storyData }), '')
216217
}
@@ -226,12 +227,21 @@ export async function waitForStorybookComponentToBeLoaded(
226227
const isStorybook = isStorybookModeFunc()
227228
if (isStorybook) {
228229
const {
230+
additionalSearchParams,
229231
clipSelector = process.env.VISUAL_STORYBOOK_CLIP_SELECTOR,
230232
id,
231233
url = process.env.VISUAL_STORYBOOK_URL,
232234
timeout = 11000,
233235
} = options
234-
await browser.url(`${url}iframe.html?id=${id}`)
236+
const baseUrl = new URL('iframe.html', url)
237+
const searchParams = new URLSearchParams({ id })
238+
if (additionalSearchParams) {
239+
for (const [key, value] of additionalSearchParams) {
240+
searchParams.append(key, value)
241+
}
242+
}
243+
baseUrl.search = searchParams.toString()
244+
await browser.url(baseUrl.toString())
235245
await $(clipSelector as string).waitForDisplayed()
236246
await browser.executeAsync(async (timeout, done) => {
237247
let timedOut = false
@@ -304,14 +314,14 @@ function filterStories(storiesJson: Stories): StorybookData[] {
304314
* Create the test files
305315
*/
306316
export function createTestFiles(
307-
{ clip, clipSelector, directoryPath, folders, framework, numShards, skipStories, storiesJson, storybookUrl }: CreateTestFileOptions,
317+
{ additionalSearchParams, clip, clipSelector, directoryPath, folders, framework, numShards, skipStories, storiesJson, storybookUrl }: CreateTestFileOptions,
308318
// For testing purposes only
309319
createTestCont = createTestContent,
310320
createFileD = createFileData,
311321
writeTestF = writeTestFile
312322
) {
313323
const fileNamePrefix = 'visual-storybook'
314-
const createTestContentData = { clip, clipSelector, folders, framework, skipStories, stories: storiesJson, storybookUrl }
324+
const createTestContentData = { additionalSearchParams, clip, clipSelector, folders, framework, skipStories, stories: storiesJson, storybookUrl }
315325

316326
if (numShards === 1) {
317327
const testContent = createTestCont(createTestContentData)

packages/visual-service/tests/storybook/__snapshots__/utils.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ exports[`Storybook utils > itFunction > generates correct mocha test code with n
123123
clipSelector: '#id',
124124
id: 'category-component--story1',
125125
storybookUrl: 'http://storybook.com/',
126+
additionalSearchParams: new URLSearchParams('foo=bar'),
126127
});
127128
await expect(browser).toMatchScreenSnapshot('category-component--story1', {"baselineFolder":"baseline/category/component/"})
128129
});
@@ -136,6 +137,7 @@ exports[`Storybook utils > itFunction > generates correct mocha test code with s
136137
clipSelector: '#id',
137138
id: 'category-component--story1',
138139
storybookUrl: 'http://storybook.com/',
140+
additionalSearchParams: new URLSearchParams('foo=bar'),
139141
});
140142
await expect(browser).toMatchScreenSnapshot('category-component--story1', {"baselineFolder":"baseline/category/component/"})
141143
});
@@ -149,6 +151,7 @@ exports[`Storybook utils > itFunction > generates correct test code with Jasmine
149151
clipSelector: '#id',
150152
id: 'category-component--story1',
151153
storybookUrl: 'http://storybook.com/',
154+
additionalSearchParams: new URLSearchParams('foo=bar'),
152155
});
153156
await expect(browser).toMatchScreenSnapshot('category-component--story1', {"baselineFolder":"baseline/category/component/"})
154157
});
@@ -162,6 +165,7 @@ exports[`Storybook utils > itFunction > generates correct test code with Jasmine
162165
clipSelector: '#id',
163166
id: 'category-component--story1',
164167
storybookUrl: 'http://storybook.com/',
168+
additionalSearchParams: new URLSearchParams('foo=bar'),
165169
});
166170
await expect(browser).toMatchScreenSnapshot('category-component--story1', {"baselineFolder":"baseline/category/component/"})
167171
});
@@ -175,6 +179,7 @@ exports[`Storybook utils > itFunction > generates correct test code with for a c
175179
clipSelector: '#id',
176180
id: 'category-component--story1',
177181
storybookUrl: 'http://storybook.com/',
182+
additionalSearchParams: new URLSearchParams('foo=bar'),
178183
});
179184
await expect($('#id')).toMatchElementSnapshot('category-component--story1-element', {"baselineFolder":"baseline/category/component/"})
180185
});
@@ -188,6 +193,7 @@ exports[`Storybook utils > itFunction > generates correct test code with for a c
188193
clipSelector: '#id',
189194
id: 'category-component--story1',
190195
storybookUrl: 'http://storybook.com/',
196+
additionalSearchParams: new URLSearchParams('foo=bar'),
191197
});
192198
await expect($('#id')).toMatchElementSnapshot('category-component--story1-element', {"baselineFolder":"baseline/category/component/"})
193199
});
@@ -201,6 +207,7 @@ exports[`Storybook utils > itFunction > generates correct test code with mocha f
201207
clipSelector: '#id',
202208
id: 'category-component--story1',
203209
storybookUrl: 'http://storybook.com/',
210+
additionalSearchParams: new URLSearchParams('foo=bar'),
204211
});
205212
await expect(browser).toMatchScreenSnapshot('category-component--story1', {"baselineFolder":"baseline/category/component/"})
206213
});
@@ -214,6 +221,7 @@ exports[`Storybook utils > itFunction > generates correct test code with mocha f
214221
clipSelector: '#id',
215222
id: 'category-component--story1',
216223
storybookUrl: 'http://storybook.com/',
224+
additionalSearchParams: new URLSearchParams('foo=bar'),
217225
});
218226
await expect(browser).toMatchScreenSnapshot('category-component--story1', {"baselineFolder":"baseline/category/component/"})
219227
});

packages/visual-service/tests/storybook/launcher.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('Visual Launcher for Storybook', () => {
5757
expect(vi.mocked(storybookUtils.isCucumberFramework)).toHaveBeenCalledOnce()
5858
expect(logInfoMock.mock.calls[0][0]).toMatchSnapshot()
5959
expect(logInfoMock.mock.calls[1][0]).toMatchSnapshot()
60-
expect(vi.mocked(storybookUtils.getArgvValue)).toHaveBeenCalledTimes(5)
60+
expect(vi.mocked(storybookUtils.getArgvValue)).toHaveBeenCalledTimes(6)
6161
expect(vi.mocked(storybookUtils.parseSkipStories)).toHaveBeenCalledWith([])
6262
expect(vi.mocked(storybookUtils.createTestFiles)).toHaveBeenCalled()
6363
expect(vi.mocked(storybookUtils.createStorybookCapabilities)).toHaveBeenCalled()
@@ -73,10 +73,11 @@ describe('Visual Launcher for Storybook', () => {
7373
.mockReturnValueOnce(false) // --clip
7474
.mockReturnValueOnce(undefined) // --clipSelector
7575
.mockReturnValueOnce(['foo-bar-foo']) // --skipStories
76+
.mockReturnValueOnce('foo=bar') // --additionalSearchParams
7677

7778
await Launcher.onPrepare(config, caps)
7879

79-
expect(vi.mocked(storybookUtils.getArgvValue)).toHaveBeenCalledTimes(5)
80+
expect(vi.mocked(storybookUtils.getArgvValue)).toHaveBeenCalledTimes(6)
8081
expect(vi.mocked(storybookUtils.parseSkipStories)).toHaveBeenCalledWith(['foo-bar-foo'])
8182
})
8283

@@ -85,11 +86,11 @@ describe('Visual Launcher for Storybook', () => {
8586
throw new Error('onPrepare method is not defined on Launcher')
8687
}
8788

88-
options.storybook = { version: 7, numShards: 16, clip: false, clipSelector: 'clipSelector', skipStories: 'skipStories' }
89+
options.storybook = { version: 7, numShards: 16, clip: false, clipSelector: 'clipSelector', skipStories: 'skipStories', additionalSearchParams: new URLSearchParams({ foo: 'bar' }) }
8990

9091
await Launcher.onPrepare(config, caps)
9192

92-
expect(vi.mocked(storybookUtils.getArgvValue)).toHaveBeenCalledTimes(5)
93+
expect(vi.mocked(storybookUtils.getArgvValue)).toHaveBeenCalledTimes(6)
9394
expect(vi.mocked(storybookUtils.parseSkipStories)).toHaveBeenCalledWith('skipStories')
9495
})
9596

packages/visual-service/tests/storybook/utils.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ describe('Storybook utils', () => {
272272
skipStories,
273273
storyData: { id: 'category-component--story1' },
274274
storybookUrl: 'http://storybook.com/',
275+
additionalSearchParams: new URLSearchParams({ foo: 'bar' })
275276
})
276277

277278
it('generates correct test code with Jasmine framework and skip array', () => {
@@ -497,6 +498,21 @@ describe('Storybook utils', () => {
497498
expect(mock$.mock.results[0].value.waitForDisplayed).toHaveBeenCalled()
498499
expect(mockBrowser.executeAsync).toHaveBeenCalled()
499500
})
501+
502+
it('should go to the correct URL when given additionalSearchParams', async () => {
503+
const mockStorybookModeFunction = vi.fn().mockReturnValue(true)
504+
const options = {
505+
url: 'http://localhost:6006/',
506+
id: 'example-component',
507+
additionalSearchParams: new URLSearchParams({ foo: 'bar', baz: 'qux' }),
508+
}
509+
510+
await waitForStorybookComponentToBeLoaded(options, mockStorybookModeFunction)
511+
512+
// Assertions
513+
expect(mockBrowser.url).toHaveBeenCalledWith('http://localhost:6006/iframe.html?id=example-component&foo=bar&baz=qux')
514+
515+
})
500516
})
501517

502518
describe('createTestFiles', () => {

packages/webdriver-image-comparison/src/helpers/options.interfaces.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ export interface ClassOptions {
103103
url?: string;
104104
// Version of the storybook, default is 7
105105
version?: number
106+
/**
107+
* Additional search parameters to be added to the Storybook URL
108+
*
109+
* @example { additionalSearchParams: new URLSearchParams({ 'foo': 'bar' }) }
110+
* This will generate the following Storybook URL for stories test: `http://storybook.url/iframe.html?id=story-id&foo=bar`
111+
*
112+
*/
113+
additionalSearchParams?: URLSearchParams;
106114
}
107115
}
108116

0 commit comments

Comments
 (0)