or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-functions.mdframework-integration.mdindex.mdportable-stories.mdstory-types.md

portable-stories.mddocs/

0

# Portable Stories

1

2

Testing utilities for using Storybook stories outside of the Storybook environment, particularly useful for unit testing and integration testing. Portable stories allow you to reuse your story definitions in testing frameworks like Jest, Vitest, or Playwright.

3

4

## Capabilities

5

6

### Project Configuration

7

8

Function for setting global project annotations to enable portable stories functionality.

9

10

```typescript { .api }

11

/**

12

* Sets the global config for portable stories functionality.

13

* Should be run once in test setup to apply global configuration like decorators.

14

*

15

* @param projectAnnotations - Project annotations (e.g., from .storybook/preview)

16

* @returns Normalized project annotations for web components renderer

17

*/

18

function setProjectAnnotations(

19

projectAnnotations:

20

| NamedOrDefaultProjectAnnotations<any>

21

| NamedOrDefaultProjectAnnotations<any>[]

22

): NormalizedProjectAnnotations<WebComponentsRenderer>;

23

```

24

25

## Usage Patterns

26

27

### Test Setup

28

29

Configure portable stories in your test setup file:

30

31

```typescript

32

// setup-file.js or setup-tests.js

33

import { setProjectAnnotations } from "@storybook/web-components";

34

import * as projectAnnotations from "../.storybook/preview";

35

36

// Apply global project configuration

37

setProjectAnnotations(projectAnnotations);

38

```

39

40

### Testing Individual Stories

41

42

Use stories directly in your tests by importing them and rendering manually:

43

44

```typescript

45

// button.test.js

46

import { render } from "@testing-library/dom";

47

import { setProjectAnnotations } from "@storybook/web-components";

48

import * as projectAnnotations from "../.storybook/preview";

49

import Meta, { Primary, Secondary } from "./Button.stories";

50

51

// Set up project annotations once

52

setProjectAnnotations(projectAnnotations);

53

54

describe("Button Component", () => {

55

it("renders primary button correctly", () => {

56

// Manually render the story

57

const element = Primary.render ? Primary.render(Primary.args, {}) : document.createElement("my-button");

58

if (Primary.args) {

59

Object.entries(Primary.args).forEach(([key, value]) => {

60

element.setAttribute(key, String(value));

61

});

62

}

63

64

render(element, { container: document.body });

65

66

const button = document.querySelector("my-button");

67

expect(button).toBeInTheDocument();

68

expect(button.getAttribute("variant")).toBe("primary");

69

});

70

71

it("handles custom args", () => {

72

const customArgs = {

73

...Primary.args,

74

label: "Custom Label",

75

disabled: true,

76

};

77

78

const element = document.createElement("my-button");

79

Object.entries(customArgs).forEach(([key, value]) => {

80

element.setAttribute(key, String(value));

81

});

82

83

render(element, { container: document.body });

84

const button = document.querySelector("my-button");

85

expect(button.getAttribute("label")).toBe("Custom Label");

86

expect(button.hasAttribute("disabled")).toBe(true);

87

});

88

89

it("applies decorators and global config", () => {

90

// Global decorators from setProjectAnnotations are available

91

// You'll need to manually apply them to your story rendering

92

const element = document.createElement("my-button");

93

if (Secondary.args) {

94

Object.entries(Secondary.args).forEach(([key, value]) => {

95

element.setAttribute(key, String(value));

96

});

97

}

98

99

render(element, { container: document.body });

100

101

expect(document.querySelector("my-button")).toBeInTheDocument();

102

});

103

});

104

```

105

106

### Testing Multiple Stories

107

108

Test multiple stories from a story file:

109

110

```typescript

111

// button.test.js

112

import * as stories from "./Button.stories";

113

114

const storyExports = Object.entries(stories)

115

.filter(([key]) => key !== 'default')

116

.map(([name, story]) => ({ name, story }));

117

118

describe("All Button Stories", () => {

119

storyExports.forEach(({ name, story }) => {

120

it(`renders ${name} story correctly`, () => {

121

const element = document.createElement("my-button");

122

123

if (story.args) {

124

Object.entries(story.args).forEach(([key, value]) => {

125

element.setAttribute(key, String(value));

126

});

127

}

128

129

render(element, { container: document.body });

130

131

expect(document.querySelector("my-button")).toBeInTheDocument();

132

});

133

});

134

});

135

```

136

137

### Integration with Testing Frameworks

138

139

#### Jest/Vitest Setup

140

141

```javascript

142

// vitest.config.js

143

import { defineConfig } from "vitest/config";

144

145

export default defineConfig({

146

test: {

147

setupFiles: ["./src/test-setup.js"],

148

environment: "jsdom",

149

},

150

});

151

152

// src/test-setup.js

153

import { setProjectAnnotations } from "@storybook/web-components";

154

import * as projectAnnotations from "../.storybook/preview";

155

156

setProjectAnnotations(projectAnnotations);

157

```

158

159

#### Playwright Integration

160

161

```typescript

162

// tests/stories.spec.ts

163

import { test, expect } from "@playwright/test";

164

import Meta, { Primary } from "../src/components/Button.stories";

165

166

test("visual regression test for primary button", async ({ page }) => {

167

// Render story in the browser

168

await page.setContent(`

169

<html>

170

<head>

171

<script type="module" src="/src/components/my-button.js"></script>

172

</head>

173

<body>

174

<my-button id="test-button"></my-button>

175

<script type="module">

176

const button = document.getElementById('test-button');

177

const args = ${JSON.stringify(Primary.args)};

178

Object.entries(args).forEach(([key, value]) => {

179

button.setAttribute(key, String(value));

180

});

181

</script>

182

</body>

183

</html>

184

`);

185

186

await page.locator("my-button").waitFor();

187

await expect(page).toHaveScreenshot("primary-button.png");

188

});

189

```

190

191

### Advanced Configuration

192

193

#### Custom Project Annotations

194

195

```typescript

196

// test-annotations.js

197

import type { Preview } from "@storybook/web-components";

198

199

const testAnnotations: Preview = {

200

decorators: [

201

// Test-specific decorators

202

(story) => {

203

const wrapper = document.createElement("div");

204

wrapper.setAttribute("data-test-wrapper", "true");

205

wrapper.appendChild(story());

206

return wrapper;

207

},

208

],

209

parameters: {

210

// Test-specific parameters

211

docs: { disable: true },

212

},

213

};

214

215

export default testAnnotations;

216

217

// test-setup.js

218

import { setProjectAnnotations } from "@storybook/web-components";

219

import previewAnnotations from "../.storybook/preview";

220

import testAnnotations from "./test-annotations";

221

222

// Combine preview and test annotations

223

setProjectAnnotations([previewAnnotations, testAnnotations]);

224

```

225

226

#### Conditional Configuration

227

228

```typescript

229

// setup-file.js

230

import { setProjectAnnotations } from "@storybook/web-components";

231

232

// Different configs for different test environments

233

if (process.env.NODE_ENV === "test") {

234

import("./test-preview").then((testConfig) => {

235

setProjectAnnotations(testConfig.default);

236

});

237

} else {

238

import("../.storybook/preview").then((previewConfig) => {

239

setProjectAnnotations(previewConfig.default);

240

});

241

}

242

```

243

244

## Benefits of Portable Stories

245

246

### Consistency

247

- **Same Components**: Test the exact same component definitions used in Storybook

248

- **Same Decorators**: Global decorators and configurations are automatically applied

249

- **Same Args**: Use the same argument validation and processing

250

251

### Efficiency

252

- **Reuse Stories**: No need to duplicate component setup in tests

253

- **Fast Feedback**: Quick unit tests without running full Storybook

254

- **CI Integration**: Run story-based tests in continuous integration

255

256

### Coverage

257

- **All Scenarios**: Easily test all story variants

258

- **Edge Cases**: Include edge case stories in automated testing

259

- **Visual Testing**: Use stories for visual regression testing

260

261

## Troubleshooting

262

263

### Common Issues

264

265

**Stories not rendering correctly:**

266

```typescript

267

// Make sure setProjectAnnotations is called before using stories

268

import { setProjectAnnotations } from "@storybook/web-components";

269

import projectAnnotations from "../.storybook/preview";

270

271

// Call this ONCE in your test setup

272

setProjectAnnotations(projectAnnotations);

273

```

274

275

**Missing decorators:**

276

```typescript

277

// Ensure your preview.js exports are properly structured

278

// .storybook/preview.js

279

export const decorators = [myDecorator];

280

export const parameters = { /* ... */ };

281

282

// Or use default export

283

export default {

284

decorators: [myDecorator],

285

parameters: { /* ... */ },

286

};

287

```

288

289

**Web Components not defined:**

290

```typescript

291

// Make sure to register your custom elements in tests

292

import "./my-button"; // Import component definition

293

294

// Or register manually

295

customElements.define("my-button", MyButtonElement);

296

```