Skip to content

Commit

Permalink
feat: playwright in ui-demo (#1359)
Browse files Browse the repository at this point in the history
* feat: upgrade sb, remove test-runner

* feat: playwright

* feat: pw tests, nft mint, auth config

* feat: ui config test

* test: add code preview configuration test for UI demo

* feat: pw commands

* fix: lint
  • Loading branch information
RobChangCA authored Feb 13, 2025
1 parent 95c6174 commit f7c7911
Show file tree
Hide file tree
Showing 11 changed files with 817 additions and 2,771 deletions.
12 changes: 10 additions & 2 deletions account-kit/react/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { dirname, join } from "path";
import type { StorybookConfig } from "@storybook/react-vite";
import react from "@vitejs/plugin-react";
import { mergeConfig } from "vite";

const config: StorybookConfig = {
stories: ["../src/**/*.@(mdx|stories.@(js|jsx|ts|tsx))"],
addons: ["@storybook/addon-essentials", "@storybook/addon-interactions"],
addons: [
getAbsolutePath("@storybook/addon-essentials"),
getAbsolutePath("@storybook/addon-interactions"),
],
framework: {
name: "@storybook/react-vite",
name: getAbsolutePath("@storybook/react-vite"),
options: {},
},

Expand All @@ -18,3 +22,7 @@ const config: StorybookConfig = {
};

export default config;

function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, "package.json")));
}
15 changes: 7 additions & 8 deletions account-kit/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,21 @@
"test:run": "vitest run --passWithNoTests"
},
"devDependencies": {
"@storybook/addon-essentials": "^8.2.8",
"@storybook/addon-interactions": "^8.4.4",
"@storybook/core-server": "^8.2.8",
"@storybook/addon-essentials": "^8.5.3",
"@storybook/addon-interactions": "^8.5.3",
"@storybook/core-server": "^8.5.3",
"@storybook/jest": "^0.2.3",
"@storybook/react-vite": "^8.2.8",
"@storybook/test": "^8.4.4",
"@storybook/test-runner": "^0.13.0",
"@storybook/react-vite": "^8.5.3",
"@storybook/test": "^8.5.3",
"@storybook/testing-library": "^0.2.2",
"@tanstack/react-query": "^5.28.9",
"autoprefixer": "^10.4.20",
"msw": "^2.4.4",
"msw-storybook-addon": "^2.0.3",
"msw-storybook-addon": "^2.0.4",
"postcss": "^8.4.45",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook": "^8.2.8",
"storybook": "^8.5.3",
"typescript": "^5.0.4",
"typescript-template": "*",
"vitest": "^2.0.4"
Expand Down
22 changes: 19 additions & 3 deletions account-kit/react/src/components/auth/sections/OAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,34 @@ export const OAuth = memo(({ ...config }: Props) => {
switch (config.authProviderId) {
case "google":
return (
<Button variant="social" icon={<GoogleIcon />} onClick={authenticate}>
<Button
variant="social"
icon={<GoogleIcon />}
onClick={authenticate}
aria-label="Google sign in"
>
Google
</Button>
);
case "facebook":
return (
<Button variant="social" icon={<FacebookIcon />} onClick={authenticate}>
<Button
variant="social"
icon={<FacebookIcon />}
onClick={authenticate}
aria-label="Facebook sign in"
>
Facebook
</Button>
);
case "apple":
return (
<Button variant="social" icon={<AppleIcon />} onClick={authenticate}>
<Button
variant="social"
icon={<AppleIcon />}
onClick={authenticate}
aria-label="Apple sign in"
>
Apple
</Button>
);
Expand All @@ -55,6 +70,7 @@ export const OAuth = memo(({ ...config }: Props) => {
</>
}
onClick={authenticate}
aria-label={`${config.displayName} sign in`}
>
{config.displayName}
</Button>
Expand Down
6 changes: 6 additions & 0 deletions examples/ui-demo/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
74 changes: 74 additions & 0 deletions examples/ui-demo/e2e/smart-contract-mint.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { test, expect } from "@playwright/test";

const googleEmail = process.env.PLAYWRIGHT_GOOGLE_EMAIL;
const googlePassword = process.env.PLAYWRIGHT_GOOGLE_PASSWORD;

test.beforeEach(async ({ page, baseURL }) => {
await page.goto(baseURL!);
});
test("Google sign in", async ({ page }) => {
if (!googleEmail || !googlePassword) {
throw new Error(
"PLAYWRIGHT_GOOGLE_EMAIL and PLAYWRIGHT_GOOGLE_PASSWORD must be set"
);
}
await expect(page).toHaveTitle(/Account Kit/);
// Enabling and disabling email to ensure config is loaded
// TODO: find a better way to determine init complete.
await page.getByRole("switch", { name: "Email" }).click();
await page.getByRole("switch", { name: "Email" }).click();
await page.locator("button[aria-label='Google sign in']").click();
const pagePromise = page.waitForEvent("popup");
const popup = await pagePromise;
await popup.waitForLoadState("networkidle");
const emailInput = await popup.getByRole("textbox");

await emailInput.fill(googleEmail);
await popup.getByRole("button", { name: /Next/i }).click();
await expect(popup.getByText(/Enter your password/i)).toBeVisible();
const passwordInput = await popup.locator("input[type='password']:visible");
await passwordInput.fill(googlePassword);
await popup.getByRole("button", { name: /Next/i }).click();

// Wait for the page to load after sign in
await expect(page.getByText(/One-click checkout/i).first()).toBeVisible();
const avatar = await page.getByRole("button", {
name: `Hello, ${googleEmail}`,
});
expect(avatar).toBeVisible();
await page.locator("img[alt='An NFT']");

// Collect NFT
await page.getByRole("button", { name: "Collect NFT" }).click();
await expect(await page.getByText("Success", { exact: true })).toBeVisible({
timeout: 30000,
});

// Check external links
await expect(
page.getByRole("link", { name: "View transaction" })
).toBeVisible();
await expect(
await page.getByRole("link", { name: "Build with Account kit" })
).toHaveAttribute(
"href",
"https://dashboard.alchemy.com/accounts?utm_source=demo_alchemy_com&utm_medium=referral&utm_campaign=demo_to_dashboard"
);
await expect(
await page.getByRole("link", { name: "Learn how." })
).toHaveAttribute("href", "https://accountkit.alchemy.com/react/sponsor-gas");
await expect(
await page.getByRole("link", { name: "View docs" })
).toHaveAttribute("href", "https://accountkit.alchemy.com/react/quickstart");
await expect(
await page.getByRole("link", { name: "Quickstart" })
).toHaveAttribute("href", "https://accountkit.alchemy.com/react/quickstart");
await expect(await page.locator("a[aria-label='GitHub']")).toHaveAttribute(
"href",
"https://github.com/alchemyplatform/aa-sdk/tree/v4.x.x"
);
await expect(await page.getByRole("link", { name: "CSS" })).toHaveAttribute(
"href",
"https://github.com/alchemyplatform/aa-sdk/blob/v4.x.x/account-kit/react/src/tailwind/types.ts#L6"
);
});
174 changes: 174 additions & 0 deletions examples/ui-demo/e2e/ui-demo-config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { test, expect } from "@playwright/test";
import path from "path";
test.beforeEach(async ({ page, baseURL }) => {
await page.goto(baseURL!);
});
test("Toggle auth methods", async ({ page }) => {
// EMAIL
const emailInput = page.getByPlaceholder("Email", { exact: true });
const emailToggle = page.getByRole("switch", { name: /email/i });
await expect(emailInput).toBeVisible();
await expect(emailToggle).toBeChecked();
await emailToggle.click();
await expect(emailToggle).not.toBeChecked();
await expect(emailInput).not.toBeVisible();

// PASSKEY
const passkeyToggle = page.getByRole("switch", { name: /passkeys/i });
const passkeyButton = page.getByRole("button", { name: "I have a passkey" });
await expect(passkeyToggle).toBeChecked();
await expect(passkeyButton).toBeVisible();
await passkeyToggle.click();
await expect(passkeyToggle).not.toBeChecked();
await expect(passkeyButton).not.toBeVisible();

// SOCIAL
const socialAuthToggle = page.getByRole("switch", { name: /social/i });

const googleButton = page.locator("button[aria-label='Google sign in']");
const googleAuthToggle = page.locator(
"button[aria-label='Google social authentication toggle']"
);

const facebookButton = page.locator("button[aria-label='Facebook sign in']");
const facebookAuthToggle = page.locator(
"button[aria-label='Facebook social authentication toggle']"
);

const discordButton = page.locator("button[aria-label='Discord sign in']");
const discordAuthToggle = page.locator(
"button[aria-label='Discord social authentication toggle']"
);

const twitterButton = page.locator("button[aria-label='Twitter sign in']");
const twitterAuthToggle = page.locator(
"button[aria-label='Twitter social authentication toggle']"
);

await expect(socialAuthToggle).toBeChecked();
await expect(googleButton).toBeVisible();
await expect(facebookButton).toBeVisible();
await expect(discordButton).toBeVisible();
await expect(twitterButton).toBeVisible();

await socialAuthToggle.click();
await expect(socialAuthToggle).not.toBeChecked();
await expect(googleButton).not.toBeVisible();
await expect(facebookButton).not.toBeVisible();
await expect(discordButton).not.toBeVisible();
await expect(twitterButton).not.toBeVisible();

await socialAuthToggle.click();

await expect(googleButton).toBeVisible();
await expect(facebookButton).toBeVisible();
await expect(discordButton).toBeVisible();
await expect(twitterButton).toBeVisible();

await googleAuthToggle.click();
await expect(googleButton).not.toBeVisible();

await facebookAuthToggle.click();
await expect(facebookButton).not.toBeVisible();

await discordAuthToggle.click();
await expect(discordButton).not.toBeVisible();

await twitterAuthToggle.click();
await expect(twitterButton).not.toBeVisible();

// EXTERNAL WALLET
const externalWalletToggle = page.getByRole("switch", {
name: /external wallets/i,
});
const externalWalletButton = page.getByRole("button", {
name: "Continue with a wallet",
});
await expect(externalWalletToggle).toBeChecked();
await expect(externalWalletButton).toBeVisible();

await externalWalletToggle.click();
await expect(externalWalletToggle).not.toBeChecked();
await expect(externalWalletButton).not.toBeVisible();

await externalWalletToggle.click();
await expect(externalWalletToggle).toBeChecked();
await expect(externalWalletButton).toBeVisible();
});

test("Branding config", async ({ page }) => {
// Dark mode
const darkModeToggle = page.locator("button[id='theme-switch']");
await expect(darkModeToggle).not.toBeChecked();
await expect(page.locator(".bg-bg-surface-default")).toHaveCSS(
"background-color",
"rgb(255, 255, 255)"
);
darkModeToggle.click();
await expect(darkModeToggle).toBeChecked();
await expect(page.locator("html")).toHaveClass("dark");
await expect(page.locator(".bg-bg-surface-default")).toHaveCSS(
"background-color",
"rgb(2, 6, 23)"
);
// Brand color
await expect(page.locator(".akui-btn-primary").first()).toHaveCSS(
"background-color",
"rgb(255, 102, 204)"
);
const brandColorButton = page.locator("button[id='color-picker']");
await brandColorButton.click();
const colorPicker = page
.locator("div")
.filter({ hasText: /^Hex$/ })
.getByRole("textbox");
await colorPicker.fill("#000000");
await expect(page.locator(".akui-btn-primary").first()).toHaveCSS(
"background-color",
"rgb(0, 0, 0)"
);
// Logo
const logoInput = page.locator('input[type="file"]');
// TODO: validate this in CI/CD
const logoPath = path.join(__dirname, "../public/next.svg");
await logoInput.setInputFiles(logoPath);
await expect(page.getByRole("img", { name: "next.svg" })).toBeVisible();
await page.locator("button[id='logo-remove']").click();
await expect(page.getByRole("img", { name: "next.svg" })).not.toBeVisible();
// Border radius
await expect(page.locator(".radius-2").first()).toHaveCSS(
"border-radius",
"16px"
);
await page.getByRole("button", { name: "Medium" }).click();
await expect(page.locator(".radius-2").first()).toHaveCSS(
"border-radius",
"32px"
);
await page.getByRole("button", { name: "Large" }).click();
await expect(page.locator(".radius-2").first()).toHaveCSS(
"border-radius",
"48px"
);
await page.getByRole("button", { name: "None" }).click();
await expect(page.locator(".radius-2").first()).toHaveCSS(
"border-radius",
"0px"
);
});

test("code preview", async ({ page }) => {
const codePreviewSwitch = page.getByRole("switch", { name: "Code preview" });
await expect(codePreviewSwitch).not.toBeChecked();
await codePreviewSwitch.click();
await expect(codePreviewSwitch).toBeChecked();
await expect(page.getByText("Export configuration")).toBeVisible();
await expect(
page.getByRole("link", { name: "Fully customize styling here." })
).toHaveAttribute(
"href",
"https://accountkit.alchemy.com/react/customization/theme"
);
await codePreviewSwitch.click();
await expect(codePreviewSwitch).not.toBeChecked();
});
5 changes: 4 additions & 1 deletion examples/ui-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:write": "prettier --no-ignore --write ./src"
"lint:write": "prettier --no-ignore --write ./src",
"test:e2e": "playwright test",
"playwright": "playwright test --ui"
},
"dependencies": {
"@account-kit/core": "^4.2.0",
Expand Down Expand Up @@ -40,6 +42,7 @@
"zustand": "^5.0.0-rc.2"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Loading

0 comments on commit f7c7911

Please sign in to comment.