You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ha...@apache.org on 2026/10/04 09:07:17 UTC
(superset) 01/04: feat(build): generate Playwright-runnable tests from Storybook stories
This is an automated email from the ASF dual-hosted git repository.
hainenber pushed a commit to branch feat/generate-playwright-runnable-tests-from-storybook-stories
in repository https://gitbox.apache.org/repos/asf/superset.git
commit cfbff8888e5f0afc2c5445c0a89aaf2ed43008c0
Author: hainenber <do...@gmail.com>
AuthorDate: Sun Oct 4 16:03:42 2026 +0700
feat(build): generate Playwright-runnable tests from Storybook stories
Signed-off-by: hainenber <do...@gmail.com>
---
.../.storybook/playwright.storybook-ci.config.ts | 36 ++++++
superset-frontend/.storybook/setup-stories.ts | 143 +++++++++++++++++++++
superset-frontend/.storybook/stories.spec.ts | 26 ++++
3 files changed, 205 insertions(+)
diff --git a/superset-frontend/.storybook/playwright.storybook-ci.config.ts b/superset-frontend/.storybook/playwright.storybook-ci.config.ts
new file mode 100644
index 00000000000..e8c0b25fbb4
--- /dev/null
+++ b/superset-frontend/.storybook/playwright.storybook-ci.config.ts
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Source: https://gist.github.com/AriPerkkio/99b9eedc7d8f71ff6e6770f9425a4be4
+import { defineConfig } from '@playwright/test';
+
+const storybookUrl = process.env.STORYBOOK_URL ?? 'http://localhost:6006';
+export default defineConfig({
+ testDir: '.',
+ globalSetup: 'setup-stories.ts',
+ use: { baseURL: storybookUrl },
+ fullyParallel: true,
+ webServer: process.env.STORYBOOK_URL
+ ? undefined
+ : {
+ command: 'npm run storybook -- --ci',
+ url: `${storybookUrl}/index.json`,
+ reuseExistingServer: true,
+ },
+});
diff --git a/superset-frontend/.storybook/setup-stories.ts b/superset-frontend/.storybook/setup-stories.ts
new file mode 100644
index 00000000000..909c0675e40
--- /dev/null
+++ b/superset-frontend/.storybook/setup-stories.ts
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Source: https://gist.github.com/AriPerkkio/99b9eedc7d8f71ff6e6770f9425a4be4
+import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { dirname, resolve } from 'node:path';
+import { type test, type Page } from '@playwright/test';
+
+declare global {
+ var __storyResult: { status: string; errors: string[] };
+}
+
+interface Entry {
+ id: string;
+ title: string;
+ name: string;
+ type: 'story' | 'docs';
+ tags?: string[];
+}
+
+interface Report {
+ type: string;
+ status: string;
+ result?: { violations?: { id: string; help: string }[] };
+}
+
+const storybookURL = process.env.STORYBOOK_URL ?? 'http://localhost:6006';
+const indexFile = resolve(
+ '../node_modules/.cache/storybook-playwright/index.json',
+);
+
+export default async function globalSetup() {
+ const response = await fetch(`${storybookURL}/index.json`);
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch ${storybookURL}/index.json: ${response.status}`,
+ );
+ }
+
+ mkdirSync(dirname(indexFile), { recursive: true });
+ writeFileSync(indexFile, await response.text());
+}
+
+function captureStoryResult() {
+ const errors: string[] = [];
+ const serialize = (error: Error) =>
+ error.stack ?? `${error.name}: ${error.message}`;
+
+ let channel: any;
+ Object.defineProperty(globalThis, '__STORYBOOK_ADDONS_CHANNEL__', {
+ configurable: true,
+ get: () => channel,
+ set: value => {
+ channel = value;
+ channel.on(
+ 'storyErrored',
+ ({ title, description }: Record<string, string>) =>
+ errors.push(`${title}\n${description}`),
+ );
+ channel.on('storyThrewException', (error: Error) =>
+ errors.push(serialize(error)),
+ );
+ channel.on('playFunctionThrewException', (error: Error) =>
+ errors.push(serialize(error)),
+ );
+ channel.on('unhandledErrorsWhilePlaying', (unhandled: Error[]) =>
+ errors.push(...unhandled.map(serialize)),
+ );
+ channel.on('storyMissing', (id: string) => {
+ globalThis.__storyResult = {
+ status: 'missing',
+ errors: [`Story "${id}" not found`],
+ };
+ });
+ channel.on(
+ 'storyFinished',
+ ({ status, reporters }: { status: string; reporters: Report[] }) => {
+ for (const report of reporters.filter(
+ report => report.status === 'failed',
+ )) {
+ const violations = report.result?.violations ?? [];
+ errors.push(
+ [
+ `${report.type} report failed`,
+ ...violations.map(v => `- ${v.id}: ${v.help}`),
+ ].join('\n'),
+ );
+ }
+ globalThis.__storyResult = { status, errors };
+ },
+ );
+ },
+ });
+}
+
+async function visitStory(page: Page, entry: Entry) {
+ await page.addInitScript(captureStoryResult);
+ await page.goto(`/iframe.html?id=${entry.id}&viewMode=story`);
+
+ const handle = await page.waitForFunction(() => globalThis.__storyResult);
+ const result = await handle.jsonValue();
+
+ if (result.status !== 'success' || result.errors.length > 0) {
+ throw new Error(
+ [
+ `Story "${entry.title} › ${entry.name}" ${result.status === 'missing' ? 'is missing' : 'failed'}.`,
+ `Open it in Storybook: ${storybookURL}/?path=/story/${entry.id}`,
+ '',
+ ...result.errors,
+ ].join('\n'),
+ );
+ }
+}
+
+export function loadStories(): [string, Parameters<typeof test>[2]][] {
+ const index = JSON.parse(readFileSync(indexFile, 'utf8')) as {
+ entries: Record<string, Entry>;
+ };
+
+ return Object.values(index.entries)
+ .filter(entry => entry.type === 'story' && entry.tags?.includes('test'))
+ .map(entry => [
+ `${entry.title} › ${entry.name}`,
+ async ({ page }) => visitStory(page, entry),
+ ]);
+}
diff --git a/superset-frontend/.storybook/stories.spec.ts b/superset-frontend/.storybook/stories.spec.ts
new file mode 100644
index 00000000000..16631a3a77c
--- /dev/null
+++ b/superset-frontend/.storybook/stories.spec.ts
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Source: https://gist.github.com/AriPerkkio/99b9eedc7d8f71ff6e6770f9425a4be4
+import { test } from '@playwright/test';
+import { loadStories } from './setup-stories';
+
+for (const [title, body] of loadStories()) {
+ test(title, body);
+}