Skip to content

Commit

Permalink
Add a Playwright e2e test-suite (pacocoursey#113)
Browse files Browse the repository at this point in the history
* add playwright setup

* add GitHub Action to execute e2e tests

* add e2e test for theme-switching and system-theme

* add placeholders for forced theme and storage event e2e tests

* add utility to create new browserContext for Playwright test

* add test for storage events to Playwright test-suite

* update system-theme test to use new helper-func

* add jest config to prevent jest running playwright tests

* add utility function to check theme stored in local-storage

* update system theme e2e test to use new util func

* rename storage-event theme test cases

* add forced-theme test-cases

* add e2e-test for basic theming use-cases
  • Loading branch information
trm217 authored May 15, 2022
1 parent 6fc9a6f commit 066ae1d
Show file tree
Hide file tree
Showing 12 changed files with 786 additions and 85 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: E2E - Test

on: push

jobs:
setup:
runs-on: ubuntu-latest
outputs:
preview_url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
steps:
- name: Wait for Vercel preview deployment to be ready
uses: patrickedqvist/wait-for-vercel-preview@v1.2.0
id: waitForVercelPreviewDeployment
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 600
test:
needs: setup
name: Run Playwright tests
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Prepare Playwright
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14"
- run: yarn
- run: npx playwright install --with-deps
- name: Run tests
run: yarn test:e2e
env:
BASE_URL: ${{ needs.setup.outputs.preview_url }}
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test
name: Unit - Test

on: push

Expand Down
23 changes: 23 additions & 0 deletions e2e/forced-theme.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { test} from '@playwright/test'
import { checkAppliedTheme, checkStoredTheme, makeBrowserContext } from './util'

test.describe('forced theme test-suite', async () => {
function makeForcedThemeTest(pageUrl: string, storedTheme: string, expectedTheme: string) {
test(
`should render forced-theme (${expectedTheme}) instead of stored theme (${expectedTheme})`,
async ({ browser, baseURL}) => {
const context = await makeBrowserContext(browser, {
baseURL,
localStorage: [{ name: 'theme', value: storedTheme }]
})
const page = await context.newPage()
await page.goto(pageUrl)

await checkStoredTheme(page, storedTheme)
await checkAppliedTheme(page, expectedTheme)
})
}

makeForcedThemeTest('/light', 'dark', 'light')
makeForcedThemeTest('/dark', 'light', 'dark')
})
65 changes: 65 additions & 0 deletions e2e/storage-event.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { test, expect } from '@playwright/test'
import { checkAppliedTheme, makeBrowserContext } from './util'

test.describe('storage-events test-suite', async () => {
test('should switch theme if stored theme value is updated in a different tab', async ({ browser, baseURL }) => {
const context = await makeBrowserContext(browser, {
colorScheme: 'light',
baseURL: baseURL,
localStorage: [
{ name: 'theme', value: 'light'}
]
})
// Create page and see if stored theme is applied
const page1 = await context.newPage()
await page1.goto('/')
await checkAppliedTheme(page1, 'light');

// Create second page and also check theme value
const page2 = await context.newPage()
await page2.goto('/')
await checkAppliedTheme(page2, 'light')

// Select theme in page2
await page2.locator('[data-test-id="theme-selector"]').selectOption('dark');
// Expect both pages to have changed theme
await checkAppliedTheme(page2,'dark')
await checkAppliedTheme(page1, 'dark')

})

test('should apply ignored storage-event once page with forced-theme is left', async ({ browser, baseURL }) => {
const context = await makeBrowserContext(browser, {
colorScheme: 'light',
baseURL: baseURL,
localStorage: [
{ name: 'theme', value: 'dark'}
]
})

// Create page and see if stored theme is applied
const page1 = await context.newPage()
await page1.goto('/')
await checkAppliedTheme(page1, 'dark');

// Create second page and also check theme value
const page2 = await context.newPage()
await page2.goto('/dark')
await checkAppliedTheme(page2, 'dark')

// Change theme on page1 and assert theme change
await page1.locator('[data-test-id="theme-selector"]').selectOption('light');
await checkAppliedTheme(page1,'light')

// Page 2 should not have changed theme since on page with forced theme
await checkAppliedTheme(page2, 'dark')
const localStorage = await page2.evaluate(() => window.localStorage)
expect(localStorage?.theme).toBe('light')

// Navigate to home and check if newly stored theme is now applied
await page2.locator("text=Go back home").click()
await page2.locator('[data-test-id="theme-selector"]').waitFor()
expect(page2.url()).toBe(baseURL + '/')
await checkAppliedTheme(page2, 'light')
})
})
44 changes: 44 additions & 0 deletions e2e/switch-theme.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { test} from '@playwright/test';
import { checkAppliedTheme, makeBrowserContext } from './util';

test.describe('basic theming test-suite', () => {

function makeRenderThemeTest(theme: string) {
test(`should render ${theme} theme`, async ({ browser, baseURL }) => {
const context = await makeBrowserContext(browser, {
baseURL,
localStorage: [{ name: 'theme', value: theme }]
});
const page = await context.newPage()

await page.goto('/');
// Select dark
await page.locator('[data-test-id="theme-selector"]').selectOption(theme);
// Check if dark theme is applied
await checkAppliedTheme(page, theme);
});
}

makeRenderThemeTest('light');
makeRenderThemeTest('dark');

function shouldUpdateTheme(initialTheme, targetTheme: string) {
test(`should switch from ${initialTheme} to ${targetTheme}-theme`, async ({ browser, baseURL }) => {
const context = await makeBrowserContext(browser, {
baseURL,
localStorage: [{ name: 'theme', value: initialTheme }]
});
const page = await context.newPage()

await page.goto('/');
await checkAppliedTheme(page, initialTheme);
// Select dark
await page.locator('[data-test-id="theme-selector"]').selectOption(targetTheme);
// Check if dark theme is applied
await checkAppliedTheme(page, targetTheme);
});
}

shouldUpdateTheme('light', 'dark');
shouldUpdateTheme('dark', 'light');
})
29 changes: 29 additions & 0 deletions e2e/system-theme.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { test, expect } from '@playwright/test'
import { checkAppliedTheme, checkStoredTheme, makeBrowserContext } from './util'

test.describe('system theme test-suite', () => {

function testSystemTheme(
pagePath: string,
preferredColorScheme: 'light' | 'dark',
expectedTheme: string
) {
test(`should render ${expectedTheme} theme if preferred-colorscheme is ${preferredColorScheme}`, async ({ browser, baseURL }) => {
const context = await makeBrowserContext(browser, {
colorScheme: preferredColorScheme,
baseURL,
localStorage: [{ name: 'theme', value: 'system' }]
})

const page = await context.newPage()
await page.goto(pagePath)

await checkStoredTheme(page, 'system')
await checkAppliedTheme(page, expectedTheme)
})
}

// Test if preferred-colorscheme works
testSystemTheme('/', 'light', 'light')
testSystemTheme('/', 'dark', 'dark')
})
36 changes: 36 additions & 0 deletions e2e/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Page, expect, Browser } from '@playwright/test'

export async function checkAppliedTheme(page: Page,theme: string) {
expect(
await page.evaluate(() => document.documentElement.getAttribute('data-theme'))
).toBe(theme);
expect(
await page.evaluate(() => document.documentElement.getAttribute('style'))
).toBe(`color-scheme: ${theme};`);
}

export async function checkStoredTheme(page: Page, expectedTheme: string) {
const localStorage = await page.evaluate(() => window.localStorage)
expect(localStorage?.theme).toBe(expectedTheme)
}

type MakeBrowserContextOptions = {
baseURL?: string,
colorScheme?: 'light' | 'dark' | 'no-preference',
localStorage?: {name: string, value: string}[]
}

export async function makeBrowserContext(browser: Browser, options: MakeBrowserContextOptions) {
return await browser.newContext({
colorScheme: options.colorScheme ?? 'no-preference',
storageState: {
cookies: [],
origins: [
{
origin: options.baseURL ?? 'http://localhost:3000',
localStorage: options.localStorage ?? [],
}
]
}
})
}
2 changes: 1 addition & 1 deletion examples/example/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Index = () => {
return (
<div>
<h1>next-themes Example</h1>
<select value={theme} onChange={e => setTheme(e.target.value)}>
<select value={theme} onChange={e => setTheme(e.target.value)} data-test-id='theme-selector'>
<option value="system">System</option>
{mounted && (
<>
Expand Down
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"scripts": {
"prepublish": "yarn build",
"build": "microbundle --jsx React.createElement --compress --no-sourcemap",
"test": "jest __tests__"
"test": "jest __tests__",
"test:e2e": "yarn playwright test"
},
"dependencies": {},
"peerDependencies": {
Expand All @@ -27,6 +28,7 @@
"@babel/preset-env": "^7.13.10",
"@babel/preset-react": "^7.12.13",
"@babel/preset-typescript": "^7.13.0",
"@playwright/test": "^1.21.1",
"@testing-library/react": "^11.2.5",
"@types/jest": "^26.0.21",
"@types/next": "^9.0.0",
Expand Down
28 changes: 28 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PlaywrightTestConfig, devices } from '@playwright/test';

const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? 'github' : 'list',
testDir: './e2e',
use: {
trace: 'on-first-retry',
baseURL: process.env.CI ? process.env.BASE_URL : 'http://localhost:3000',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
};

export default config;
Loading

1 comment on commit 066ae1d

@vercel
Copy link

@vercel vercel bot commented on 066ae1d May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-themes – ./

next-themes-trm217.vercel.app
next-themes-git-main-trm217.vercel.app
next-themes-tan.vercel.app

Please sign in to comment.