Skip to content

Commit

Permalink
feat(web): add e2e testing (#1040)
Browse files Browse the repository at this point in the history
* install playwright

* wip: e2e config

* add playwright to .git ignore

* update vite.config

* update gitignore files

* add workspaceId

* wip: e2e config

* fix e2e action env

* fix: removed unused env vars

* fix: @playwright/test version

* add gql to Reearth type
  • Loading branch information
nourbalaha authored and yk-eukarya committed Oct 1, 2024
1 parent 188d925 commit b762e66
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 2 deletions.
6 changes: 6 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ amplifytools.xcconfig
.secret-*
**.sample
#amplify-do-not-edit-end

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
6 changes: 6 additions & 0 deletions web/e2e/dashboard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from "@reearth-cms/e2e/utils";

test("Home page is displayed", async ({ reearth, page }) => {
await reearth.goto("/", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("textbox")).toBeVisible();
});
19 changes: 19 additions & 0 deletions web/e2e/utils/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const config = {
api: process.env["REEARTH_CMS_API"],
userName: process.env["REEARTH_CMS_E2E_USERNAME"],
password: process.env["REEARTH_CMS_E2E_PASSWORD"],
workspaceId: process.env["REEARTH_CMS_E2E_WORKSPACE_ID"],
authAudience: process.env["REEARTH_CMS_AUTH0_AUDIENCE"],
authClientId: process.env["REEARTH_CMS_AUTH0_CLIENT_ID"],
authUrl: process.env["REEARTH_CMS_AUTH0_DOMAIN"],
};

export type Config = typeof config;

export function setAccessToken(accessToken: string) {
process.env.REEARTH_E2E_ACCESS_TOKEN = accessToken;
}

export function getAccessToken(): string | undefined {
return process.env.REEARTH_E2E_ACCESS_TOKEN;
}
55 changes: 55 additions & 0 deletions web/e2e/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// eslint-disable-next-line no-restricted-imports
import { test as base, type Page } from "@playwright/test";

import { config, getAccessToken, type Config } from "./config";

// eslint-disable-next-line no-restricted-imports
export { expect } from "@playwright/test";

export type Reearth = {
goto: Page["goto"];
token: string | undefined;
gql: <T = any>(
query: string,
variables?: Record<string, any>,
options?: { ignoreError?: boolean },
) => Promise<T>;
} & Config;

export const test = base.extend<{
reearth: Reearth;
}>({
reearth: async ({ page, request }, use) => {
use({
...config,
token: getAccessToken(),
async goto(url, options) {
const res = await page.goto(url, options);
if (this.token) {
await page.evaluate(`window.REEARTH_E2E_ACCESS_TOKEN = ${JSON.stringify(this.token)};`);
}
return res;
},
async gql(query, variables, options) {
if (!this.token) throw new Error("access token is not initialized");

const resp = await request.post(config.api + "/graphql", {
data: {
query,
variables,
},
headers: {
Authorization: `Bearer ${this.token}`,
},
});

const body = await resp.json();
if (!options?.ignoreError && (!resp.ok() || body.errors)) {
throw new Error(`GraphQL error: ${JSON.stringify(body)}`);
}

return body;
},
});
},
});
58 changes: 58 additions & 0 deletions web/e2e/utils/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import axios from "axios";

import { config } from "./config";

export async function login(): Promise<string> {
const { authUrl, userName, password, authAudience, authClientId } = config;

if (!authUrl || !userName || !password || !authAudience || !authClientId) {
throw new Error(
`either authUrl, userName, password, authAudience and authClientId are missing: ${JSON.stringify(
{
authUrl,
userName,
password: password ? "***" : "",
authAudience,
authClientId,
},
)}`,
);
}

try {
const resp = await axios.post<{ access_token?: string }>(
`${oauthDomain(authUrl)}/oauth/token`,
{
username: userName,
password,
audience: authAudience,
client_id: authClientId,
grant_type: "password",
scope: "openid profile email",
},
);

if (!resp.data.access_token) {
throw new Error("access token is missing");
}
return resp.data.access_token;
} catch (e) {
throw new Error(
`${e}, config=${JSON.stringify({
authUrl,
userName,
password: password ? "***" : "",
authAudience,
authClientId,
})}`,
);
}
}

function oauthDomain(u: string | undefined): string {
if (!u) return "";
if (!u.startsWith("https://") && !u.startsWith("http://")) {
u = "https://" + u;
}
return u.endsWith("/") ? u.slice(0, -1) : u;
}
8 changes: 8 additions & 0 deletions web/e2e/utils/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getAccessToken, setAccessToken } from "./config";
import { login } from "./login";

export default async function globalSetup() {
if (!getAccessToken()) {
setAccessToken(await login());
}
}
2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"preview": "vite preview",
"test": "vitest",
"coverage": "vitest run --coverage",
"e2e": "playwright test",
"lint": "eslint .",
"format": "eslint . --fix",
"type": "tsc",
Expand All @@ -34,6 +35,7 @@
"@graphql-codegen/typescript-operations": "3.0.4",
"@graphql-codegen/typescript-react-apollo": "3.3.7",
"@mapbox/vector-tile": "1.3.1",
"@playwright/test": "1.41.1",
"@rollup/plugin-yaml": "4.1.2",
"@storybook/addon-essentials": "7.6.4",
"@storybook/addon-interactions": "7.6.4",
Expand Down
17 changes: 17 additions & 0 deletions web/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type PlaywrightTestConfig } from "@playwright/test";
import dotenv from "dotenv";

dotenv.config();

const config: PlaywrightTestConfig = {
use: {
baseURL: process.env.REEARTH_CMS_E2E_BASEURL || "http://localhost:3000/",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
testDir: "e2e",
globalSetup: "./e2e/utils/setup.ts",
reporter: process.env.CI ? "github" : "list",
};

export default config;
8 changes: 7 additions & 1 deletion web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@
"jsx": "react-jsx",
"baseUrl": ".",
"paths":{
"@reearth-cms/e2e/*": [
"e2e/*"
],
"@reearth-cms/*": [
"src/*"
]
}
},
"include": ["src"],
"include": [
"src",
"e2e",
],
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
Expand Down
2 changes: 2 additions & 0 deletions web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import react from "@vitejs/plugin-react";
import { readEnv } from "read-env";
import { defineConfig, loadEnv, type Plugin } from "vite";
import cesium from "vite-plugin-cesium";
import { configDefaults } from "vitest/config";

// https://vitejs.dev/config/
export default defineConfig({
Expand Down Expand Up @@ -38,6 +39,7 @@ export default defineConfig({
test: {
environment: "jsdom",
setupFiles: "./src/test/setup.ts",
exclude: [...configDefaults.exclude, "e2e/*"],
coverage: {
all: true,
include: ["src/**/*.ts", "src/**/*.tsx"],
Expand Down
23 changes: 22 additions & 1 deletion web/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5651,6 +5651,13 @@
picocolors "^1.0.0"
tslib "^2.6.0"

"@playwright/[email protected]":
version "1.41.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.41.1.tgz#6954139ed4a67999f1b17460aa3d184f4b334f18"
integrity sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==
dependencies:
playwright "1.41.1"

"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
Expand Down Expand Up @@ -11198,7 +11205,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==

fsevents@^2.3.2, fsevents@~2.3.2:
fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
Expand Down Expand Up @@ -14985,6 +14992,20 @@ pkg-types@^1.0.3:
mlly "^1.2.0"
pathe "^1.1.0"

[email protected]:
version "1.41.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.41.1.tgz#9c152670010d9d6f970f34b68e3e935d3c487431"
integrity sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==

[email protected]:
version "1.41.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.41.1.tgz#83325f34165840d019355c2a78a50f21ed9b9c85"
integrity sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==
dependencies:
playwright-core "1.41.1"
optionalDependencies:
fsevents "2.3.2"

polished@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1"
Expand Down

0 comments on commit b762e66

Please sign in to comment.