Skip to content

Commit c2247c0

Browse files
feat(repo): Add component visual diffing tests (#6194)
1 parent 1367d39 commit c2247c0

27 files changed

+169
-63
lines changed

.changeset/floppy-taxis-change.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/clerk-js/playwright.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const baseURL = `http://localhost:${PORT}`;
1616
*/
1717
export default defineConfig({
1818
testDir: './sandbox/integration',
19+
/* Global setup for Clerk testing */
20+
globalSetup: './sandbox/integration/global.setup.ts',
1921
/* Run tests in files in parallel */
2022
fullyParallel: false,
2123
/* Fail the build on CI if you accidentally left test.only in the source code. */
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { signInWithEmailCode } from './helpers';
4+
5+
const rootElement = '.cl-createOrganization-root';
6+
7+
test('create organization', async ({ page }) => {
8+
await signInWithEmailCode(page);
9+
await page.goto('/create-organization');
10+
await page.waitForSelector(rootElement, { state: 'attached' });
11+
await expect(page.locator(rootElement)).toHaveScreenshot('create-organization.png');
12+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { clerkSetup } from '@clerk/testing/playwright';
2+
3+
import { instanceKeys } from '../../../../integration/presets/envs';
4+
5+
/**
6+
* Global setup function for Playwright tests in the sandbox environment.
7+
* Configures Clerk authentication keys from the sandbox instance and initializes
8+
* the Clerk testing environment before running integration tests.
9+
*
10+
* @throws {Error} When sandbox instance keys are not found
11+
*/
12+
async function globalSetup(): Promise<void> {
13+
const keys = instanceKeys.get('sandbox');
14+
15+
if (!keys) throw new Error('Sandbox instance keys not found');
16+
17+
process.env.CLERK_SECRET_KEY = keys.sk;
18+
process.env.CLERK_PUBLISHABLE_KEY = keys.pk;
19+
20+
await clerkSetup();
21+
}
22+
23+
export default globalSetup;
Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,21 @@
11
import { clerk } from '@clerk/testing/playwright';
2-
import type { Page } from '@playwright/test';
2+
import { type Page } from '@playwright/test';
33

4-
export async function disableColorMix(page: Page) {
5-
await page.addInitScript(() => {
6-
window.ClerkCSSSupport = {
7-
...window.ClerkCSSSupport,
8-
colorMix: false,
9-
};
10-
});
11-
}
12-
13-
export async function waitForClerkLoaded(page: Page, selector: string) {
4+
/**
5+
* Signs in a user using email code strategy for integration tests.
6+
* Navigates to sign-in page, waits for Clerk to load, then performs authentication.
7+
* @param page - The Playwright page instance
8+
* @example
9+
* ```ts
10+
* await signInWithEmailCode(page);
11+
* await page.goto('/protected-page');
12+
* ```
13+
*/
14+
export async function signInWithEmailCode(page: Page): Promise<void> {
15+
await page.goto('/sign-in');
1416
await clerk.loaded({ page });
15-
await page.waitForSelector(selector, { state: 'attached' });
16-
}
17-
18-
type ColorMixRunnerOptions = {
19-
path: string;
20-
waitForClerkElement: string;
21-
fn: (page: Page) => Promise<void>;
22-
};
23-
24-
export function createColorMixRunner({ path, waitForClerkElement, fn }: ColorMixRunnerOptions) {
25-
return async (page: Page) => {
26-
await page.goto(path);
27-
await waitForClerkLoaded(page, waitForClerkElement);
28-
await fn(page);
29-
30-
await disableColorMix(page);
31-
32-
await page.reload();
33-
await waitForClerkLoaded(page, waitForClerkElement);
34-
await fn(page);
35-
};
17+
await clerk.signIn({
18+
page,
19+
signInParams: { strategy: 'email_code', identifier: '[email protected]' },
20+
});
3621
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { clerk } from '@clerk/testing/playwright';
2+
import { expect, test } from '@playwright/test';
3+
4+
const rootElement = '.cl-oauthConsent-root';
5+
6+
test('oauth consent', async ({ page }) => {
7+
await page.goto('/oauth-consent');
8+
await clerk.loaded({ page });
9+
await page.waitForSelector(rootElement, { state: 'attached' });
10+
await expect(page.locator(rootElement)).toHaveScreenshot('oauth-consent.png');
11+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { signInWithEmailCode } from './helpers';
4+
5+
const rootElement = '.cl-organizationSwitcher-root';
6+
const triggerElement = '.cl-organizationSwitcherTrigger';
7+
const popoverElement = '.cl-organizationSwitcherPopoverCard';
8+
const actionElement = '.cl-organizationSwitcherPopoverActionButton__createOrganization';
9+
10+
test('organization switcher', async ({ page }) => {
11+
await signInWithEmailCode(page);
12+
await page.goto('/organization-switcher');
13+
await page.waitForSelector(rootElement, { state: 'attached' });
14+
await page.locator(triggerElement).click();
15+
await page.waitForSelector(popoverElement, { state: 'visible' });
16+
await expect(page.locator(popoverElement)).toHaveScreenshot('organization-switcher-popover.png');
17+
await page.locator(actionElement).hover();
18+
await expect(page.locator(popoverElement)).toHaveScreenshot('organization-switcher-action-hover.png');
19+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { signInWithEmailCode } from './helpers';
4+
5+
const rootElement = '.cl-organizationList-root';
6+
7+
test('organization list', async ({ page }) => {
8+
await signInWithEmailCode(page);
9+
await page.goto('/organization-list');
10+
await page.waitForSelector(rootElement, { state: 'attached' });
11+
await expect(page.locator(rootElement)).toHaveScreenshot('organization-list.png');
12+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { clerk } from '@clerk/testing/playwright';
2+
import { expect, test } from '@playwright/test';
3+
4+
const rootElement = '.cl-modalBackdrop';
5+
const buttonElement = 'button:has-text("Open Sign In")';
6+
7+
test('sign in modal', async ({ page }) => {
8+
await page.goto('/open-sign-in');
9+
await clerk.loaded({ page });
10+
await page.locator(buttonElement).click();
11+
await page.waitForSelector(rootElement, { state: 'visible' });
12+
await expect(page.locator(rootElement)).toHaveScreenshot('sign-in-modal.png');
13+
});
Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import type { Page } from '@playwright/test';
1+
import { clerk } from '@clerk/testing/playwright';
22
import { expect, test } from '@playwright/test';
33

4-
import { createColorMixRunner } from './helpers';
4+
const rootElement = '.cl-signIn-root';
5+
const primaryButtonElement = '.cl-formButtonPrimary';
6+
const secondaryButtonElement = '.cl-socialButtonsBlockButton__google';
7+
const actionLinkElement = '.cl-footerActionLink';
58

6-
const signInRootElement = '.cl-signIn-root';
7-
8-
const runner = createColorMixRunner({
9-
path: '/sign-in',
10-
waitForClerkElement: signInRootElement,
11-
fn: async (page: Page) => {
12-
await expect(page.locator(signInRootElement)).toHaveScreenshot('sign-in.png');
13-
},
14-
});
15-
16-
test('sign-in', async ({ page }) => {
17-
await runner(page);
9+
test('sign in', async ({ page }) => {
10+
await page.goto('/sign-in');
11+
await clerk.loaded({ page });
12+
await page.waitForSelector(rootElement, { state: 'attached' });
13+
await expect(page.locator(rootElement)).toHaveScreenshot('sign-in.png');
14+
await page.locator(primaryButtonElement).hover();
15+
await expect(page.locator(rootElement)).toHaveScreenshot('sign-in-primary-button-hover.png');
16+
await page.locator(secondaryButtonElement).hover();
17+
await expect(page.locator(rootElement)).toHaveScreenshot('sign-in-secondary-button-hover.png');
18+
await page.locator(actionLinkElement).hover();
19+
await expect(page.locator(rootElement)).toHaveScreenshot('sign-in-action-link-hover.png');
1820
});
Loading
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { clerk } from '@clerk/testing/playwright';
2+
import { expect, test } from '@playwright/test';
3+
4+
const rootElement = '.cl-signUp-root';
5+
6+
test('sign up', async ({ page }) => {
7+
await page.goto('/sign-up');
8+
await clerk.loaded({ page });
9+
await page.waitForSelector(rootElement, { state: 'attached' });
10+
await expect(page.locator(rootElement)).toHaveScreenshot('sign-up.png');
11+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { signInWithEmailCode } from './helpers';
4+
5+
const rootElement = '.cl-userButton-root';
6+
const triggerElement = '.cl-userButtonTrigger';
7+
const popoverElement = '.cl-userButtonPopoverCard';
8+
9+
test('user button', async ({ page }) => {
10+
await signInWithEmailCode(page);
11+
await page.goto('/user-button');
12+
await page.waitForSelector(rootElement, { state: 'attached' });
13+
await page.locator(triggerElement).click();
14+
await page.waitForSelector(popoverElement, { state: 'visible' });
15+
await expect(page.locator(popoverElement)).toHaveScreenshot('user-button-popover.png');
16+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { signInWithEmailCode } from './helpers';
4+
5+
const rootElement = '.cl-userProfile-root';
6+
7+
test('user profile', async ({ page }) => {
8+
// Set a larger viewport to capture the full user profile
9+
await page.setViewportSize({ width: 1600, height: 900 });
10+
11+
await signInWithEmailCode(page);
12+
await page.goto('/user-profile');
13+
await page.waitForSelector(rootElement, { state: 'attached' });
14+
await expect(page.locator(rootElement)).toHaveScreenshot('user-profile.png');
15+
});

pnpm-lock.yaml

Lines changed: 0 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)