Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add E2E tests #53

Merged
merged 8 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,21 @@ jobs:
cache: npm
- run: npm ci
- run: npm run test
e2e:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npm run test:e2e
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
26 changes: 26 additions & 0 deletions .github/workflows/update-snapshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

on:
push:
branches:
- main

jobs:
playwright:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npm run test:e2e -- --update-snapshots
- run: |
git config user.name "GitHub actions"
git config user.email action@github.com
git add .
git commit -m "ci: update snapshots"
git push
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,6 @@ dist.zip

dist-ssr
*.local
/test-results/
/playwright-report/
/playwright/.cache/
45 changes: 45 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"scripts": {
"test:dev": "vitest",
"test": "vitest run",
"test:e2e": "playwright test --project=chromium",
"preversion": "node ./scripts/bump-manifest.cjs $npm_new_version && git add manifest.json",
"prebuild": "rm -rf dist dist.zip",
"build": "tsc && vite build",
Expand All @@ -22,6 +23,7 @@
"license": "MIT",
"devDependencies": {
"@crxjs/vite-plugin": "^1.0.13",
"@playwright/test": "^1.30.0",
"@types/chrome": "^0.0.184",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
Expand Down
56 changes: 56 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { PlaywrightTestConfig, defineConfig, devices } from "@playwright/test";

const ciConfig: Partial<PlaywrightTestConfig> = {
forbidOnly: true,
retries: 2,
workers: 1,
reporter: "github",
use: {
actionTimeout: 30000,
trace: "on-first-retry",
screenshot: "on",
},
};
const localConfig: Partial<PlaywrightTestConfig> = {
forbidOnly: false,
retries: 0,
workers: undefined,
reporter: "dot",
use: {
actionTimeout: 0,
trace: "on-first-retry",
screenshot: "on",
},
};

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
viewport: { width: 360, height: 660 },
},
},
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
outputDir: "test-results/",

...(process.env.CI ? ciConfig : localConfig),
});
4 changes: 2 additions & 2 deletions src/components/AppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ export function AppBar(props: Props) {
{isAuthenticated ? (
<>
<Tooltip title={t("refresh")}>
<IconButton onClick={onRefresh}>
<IconButton onClick={onRefresh} data-testid="refresh-button">
<RepeatIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("signOut")}>
<IconButton onClick={onSignOut}>
<IconButton onClick={onSignOut} data-testid="signout-button">
<LogoutIcon />
</IconButton>
</Tooltip>
Expand Down
4 changes: 3 additions & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export function Footer() {
return (
<Container component="footer">
<Typography variant="body2">
&copy; {t("author")} | {t("version", [PACKAGE_VERSION])} |{" "}
&copy; {t("author")} |{" "}
<span data-testid="app-version">{t("version", [PACKAGE_VERSION])}</span>{" "}
|{" "}
<a href={URL_PRIVACY_POLICY} target="_blank" rel="noopener">
{t("privacyPolicy")}
</a>
Expand Down
8 changes: 6 additions & 2 deletions src/components/UnauthorizedAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ export function UnauthorizedAlert(props: Props) {
const { t } = useI18n();

return (
<Alert color="warning">
<Alert color="warning" data-testid="unauthorized-warning">
<Typography>{t("unAuthorized")}</Typography>
<Button onClick={onSignIn} variant="text">
<Button
onClick={onSignIn}
variant="text"
data-testid="signin-with-google-button"
>
<img
src={chrome.runtime.getURL(googleSigninDarkNormal)}
height="48"
Expand Down
36 changes: 36 additions & 0 deletions tests/ExtensionPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Page } from "playwright-core";

export class ExtensionPage {
static async from(
page: Page,
extensionId: string,
initScript?: () => void
): Promise<ExtensionPage> {
// Inject mock Chrome Manifest v3 API
await page.addInitScript(initScript ?? (() => {}));

await page.goto(`chrome-extension://${extensionId}/src/popup.html`);

// mask app version to remove unintended visual diffs
await page
.getByTestId("app-version")
.evaluate((el) => (el.innerHTML = "vX.X.X"));

return new ExtensionPage(page);
}

constructor(public readonly page: Page) {}

async signIn(): Promise<void> {
await this.page.getByTestId("signin-with-google-button").click();
}

async signOut(): Promise<void> {
await this.page.getByTestId("signout-button").click();
await this.page.getByTestId("signin-with-google-button").waitFor();
}

hasUnauthorizedWarning(): Promise<boolean> {
return this.page.getByTestId("unauthorized-warning").isVisible();
}
}
37 changes: 37 additions & 0 deletions tests/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ExtensionPage } from "./ExtensionPage.js";
import { test, expect } from "./fixture.js";

test("The sign-in button should be visible on the first view", async ({
page,
extensionId,
}) => {
const extPage = await ExtensionPage.from(page, extensionId);
await expect(await extPage.hasUnauthorizedWarning()).toEqual(true);
await expect(extPage.page).toHaveScreenshot();
});

test("The sign-in button should not be visible if already signed-in", async ({
page,
extensionId,
}) => {
const extPage = await ExtensionPage.from(page, extensionId, () => {
chrome.identity.getAuthToken = (
_: unknown,
callback?: (token: string) => void
) => callback?.("xxx");
});
await expect(await extPage.hasUnauthorizedWarning()).toEqual(false);
await expect(extPage.page).toHaveScreenshot();
});

test("sign-out should work", async ({ page, extensionId }) => {
const extPage = await ExtensionPage.from(page, extensionId, () => {
chrome.identity.getAuthToken = (
_: unknown,
callback?: (token: string) => void
) => callback?.("xxx");
});
await expect(await extPage.hasUnauthorizedWarning()).toEqual(false);
await extPage.signOut();
await expect(await extPage.hasUnauthorizedWarning()).toEqual(true);
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading