Skip to content

Commit

Permalink
Merge branch 'main' into feat/project-import-export
Browse files Browse the repository at this point in the history
  • Loading branch information
hexaforce authored Sep 18, 2024
2 parents 3a479b0 + 62c5a82 commit a798a4e
Show file tree
Hide file tree
Showing 24 changed files with 392 additions and 25 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/pr_title.yml → .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: PR Title Checker
name: PR
on:
pull_request:
types:
Expand Down Expand Up @@ -31,3 +31,8 @@ jobs:
The subject "{subject}" found in the pull request title "{title}"
didn't match the configured pattern. Please ensure that the subject
doesn't start with an uppercase character.
assign-author:
runs-on: ubuntu-latest
steps:
- uses: toshimaru/[email protected]
4 changes: 0 additions & 4 deletions server/pkg/builtin/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ extensions:
label: Labelled
- key: default_road
label: Road Map
- key: stamen_watercolor
label: Stamen Watercolor
- key: stamen_toner
label: Stamen Toner
- key: open_street_map
label: OpenStreetMap
- key: esri_world_topo
Expand Down
2 changes: 0 additions & 2 deletions server/pkg/builtin/manifest_ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ extensions:
default: デフォルト
default_label: ラベル付き地図
default_road: 道路地図
stamen_watercolor: Stamen Watercolor
stamen_toner: Stamen Toner
open_street_map: OpenStreetMap
esri_world_topo: ESRI Topography
black_marble: Black Marble
Expand Down
7 changes: 6 additions & 1 deletion web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ REEARTH_WEB_AUTH0_AUDIENCE=
REEARTH_WEB_AUTH0_CLIENT_ID=

# Optional
REEARTH_WEB_CESIUM_ION_TOKEN_URL=
REEARTH_WEB_CESIUM_ION_TOKEN_URL=

# E2E for playwright
REEARTH_WEB_E2E_BASEURL=
REEARTH_WEB_E2E_ACCOUNT=
REEARTH_WEB_E2E_ACCOUNT_PASSWORD=
2 changes: 2 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ __screenshots__
/cesium_ion_token.txt
.idea/*
.yarn/*
e2e/utils/.auth
e2e/testSnapshot
90 changes: 90 additions & 0 deletions web/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Playwright E2E Testing Project

## Project Overview

This project is an end-to-end (E2E) testing suite built using [Playwright](https://playwright.dev/). It follows the Page Object Model (POM) design pattern to organize tests for various components of the `visualizer` module, including `dashboard`, `projectSetting`, and `editor`.

## Project Structure

web
├── e2e
│ ├── pom
│ │ ├── visualizer
│ │ │ ├── dashboard
│ │ │ │ ├── index.ts
│ │ │ │ └── ProjectsPage.ts
│ │ │ ├── projectSetting
│ │ │ │ ├── generalPage.ts
│ │ │ │ └── index.ts
│ │ │ ├── editor
│ │ │ │ ├── index.ts
│ │ │ │ └── MapPage.ts
│ │ └── index.ts
│ ├── utils
│ │ ├── .auth
│ │ ├── config.ts //old setting
│ │ ├── index.ts //old setting
│ │ ├── login.ts //old setting
│ │ ├── setup.ts //old setting
│ │ └── auth.setup.ts
│ ├── dashboard.spec.ts //old testing
│ └── test.spec.ts //testing file
└── README.md

## Prerequisites

- [Node.js](https://nodejs.org/) >= 14.x
- [npm](https://www.npmjs.com/) or [Yarn](https://yarnpkg.com/)

## Setup

### Install Dependencies

To install the required packages, run the following commands:

```bash
cd web
yarn install
npx playwright install //if needed
```

To set required .env for frontend. Recording following variable.

```bash
# Local backend with Auth0 OSS tenant
REEARTH_WEB_E2E_BASEURL=http://localhost:3000
REEARTH_WEB_E2E_ACCOUNT // your visualizer accounnt
REEARTH_WEB_E2E_ACCOUNT_PASSWORD // your visualizer password
```

## Running Tests

First run api and frontend, And to run all tests:

```bash
yarn e2e
```

## Configuration

- utils/auth.setup.ts

Contains the authentication setup process for the tests. You can define login flows here to ensure tests are executed under authenticated conditions.

## Snapshots

Visual regression testing snapshots will be storied in `e2e/__screenshots__` directory. Test will fail on first run if you don't have the snapshots locally.

## Page Object Model (POM)

The project follows the Page Object Model (POM) pattern, where each page is represented as an object. This improves code maintainability and reusability.

Key Directories:

- dashboard: Manages interactions with the dashboard page via ProjectsPage.ts.
- projectSetting: Handles interactions with the project settings page (generalPage.ts).
- editor: Contains methods for the editor page, including MapPage.ts.

## Memo

We need to improve the implementation later.
40 changes: 40 additions & 0 deletions web/e2e/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// globalSetup.js
import path, { dirname } from "path";
import { fileURLToPath } from "url";

import { chromium, expect } from "@reearth/e2e/utils";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const authFile = path.join(__dirname, "./utils/.auth/user.json");

export default async () => {
if (
!process.env.REEARTH_WEB_E2E_ACCOUNT ||
!process.env.REEARTH_WEB_E2E_ACCOUNT_PASSWORD
) {
throw new Error(
"please setup .env for REEARTH_WEB_E2E_ACCOUNT and REEARTH_WEB_E2E_ACCOUNT_PASSWORD"
);
}

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(
process.env.REEARTH_WEB_E2E_BASEURL || "http://localhost:3000/"
);
await page
.getByPlaceholder("username/email")
.fill(process.env.REEARTH_WEB_E2E_ACCOUNT);
await page
.getByPlaceholder("your password")
.fill(process.env.REEARTH_WEB_E2E_ACCOUNT_PASSWORD);
await page.getByText("LOG IN").click();
await page.waitForTimeout(10 * 1000);
await page.goto(
process.env.REEARTH_WEB_E2E_BASEURL || "http://localhost:3000/"
);
await expect(page.getByRole("button", { name: "New Project" })).toBeVisible();
await page.context().storageState({ path: authFile });
await browser.close();
};
13 changes: 7 additions & 6 deletions web/e2e/dashboard.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { expect, test } from "@reearth/e2e/utils";
// not in use
// import { expect, test } from "@reearth/e2e/utils";

test("dasboard can be logged in", async ({ page, reearth }) => {
await reearth.initUser();
await reearth.goto(`/dashboard/${reearth.teamId}`);
// test("dasboard can be logged in", async ({ page, reearth }) => {
// await reearth.initUser();
// await reearth.goto(`/dashboard/${reearth.teamId}`);

await expect(page.getByText(`${reearth.userName}'s workspace`)).toBeVisible();
});
// await expect(page.getByText(`${reearth.userName}'s workspace`)).toBeVisible();
// });
64 changes: 64 additions & 0 deletions web/e2e/editorMapAddLayer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import path, { dirname } from "path";
import { fileURLToPath } from "url";

import { expect, test } from "@reearth/e2e/utils";
import { v4 as uuidv4 } from "uuid";

import pom from "./pom";

//use session, also could set in playwright config
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const authFile = path.join(__dirname, "./utils/.auth/user.json");
test.use({ storageState: authFile });

//test
test("create project and add layer", async ({ page }) => {
const url = process.env.REEARTH_WEB_E2E_BASEURL || "http://localhost:3000/";

await page.goto(url);
await page.waitForSelector("button:text('New Project')");

// create new project and get in
const dashboardPage = new pom.visualizer.dashboard.ProjectsPage(page);
const projectName = "playwright_test_" + uuidv4();
await dashboardPage.createProject(projectName);
await page.waitForSelector(`div:text('${projectName}')`);
await dashboardPage.dbClickInProjectByName(projectName);

// clocs sky box, add new layer
const mapPage = new pom.visualizer.editor.MapPage(page);
const layerTitle = "layer_" + uuidv4();
await mapPage.closeSkyBox();
await page.waitForTimeout(8 * 1000);

// take screenshot
const canvas = page.locator("canvas");
expect.soft(await canvas.screenshot()).toMatchSnapshot("before-action.png", {
maxDiffPixels: 100
});

await mapPage.createNewLayerBySketch(layerTitle);

//add circle
await page.getByText(layerTitle).click();
await mapPage.circleButton.click();
const { x, y } = await mapPage.getLocatorOfCanvs();
await mapPage.createCircleToEarthByLocator(x, y);

//wait result for stateble
await page.waitForTimeout(10 * 1000);

expect
.soft(await canvas.screenshot())
.not.toMatchSnapshot("before-action.png");
expect.soft(await canvas.screenshot()).toMatchSnapshot("after-action.png");

//delete created project
await dashboardPage.goto();
await dashboardPage.intoProjectSettingByName(projectName);
const generalSettingPage = new pom.visualizer.projectSetting.generalPage(
page
);
await generalSettingPage.deleteProject();
});
5 changes: 5 additions & 0 deletions web/e2e/pom/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as visualizer from "./visualizer";

export default {
visualizer
};
39 changes: 39 additions & 0 deletions web/e2e/pom/visualizer/dashboard/ProjectsPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Page, Locator } from "@reearth/e2e/utils";

export default class ProjectsPage {
readonly page: Page;
readonly newProjectButton: Locator;
readonly projectNameInput: Locator;
readonly applyButton: Locator;

constructor(page: Page) {
this.page = page;
this.newProjectButton = this.page.getByText("New Project");
this.projectNameInput = this.page.getByPlaceholder("Text");
this.applyButton = this.page.getByText("Apply");
}

async goto() {
await this.page.goto(`/`);
}

async createProject(name: string) {
await this.newProjectButton.click();
await this.projectNameInput.fill(name);
await this.applyButton.click();
}

async dbClickInProjectByName(name: string) {
await this.page
.locator(`div:has(> div > div > div:text('${name}') ) > :first-child`)
.dblclick();
}

async intoProjectSettingByName(name: string) {
await this.page
.locator(`div:has(> div > div:text('${name}')) button`)
.click();
await this.page.getByText("Project Setting").click();
await this.page.waitForSelector("p:text('Project Name')");
}
}
3 changes: 3 additions & 0 deletions web/e2e/pom/visualizer/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ProjectsPage from "./ProjectsPage";

export { ProjectsPage };
58 changes: 58 additions & 0 deletions web/e2e/pom/visualizer/editor/MapPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Page, Locator } from "@reearth/e2e/utils";

export default class MapPage {
readonly page: Page;
readonly newLayerButton: Locator;
readonly addSketchLayerButton: Locator;
readonly layerNameInput: Locator;
readonly createButton: Locator;
readonly circleButton: Locator;
readonly skyBoxSelectButton: Locator;

constructor(page: Page) {
this.page = page;
this.newLayerButton = this.page.getByText("New Layer");
this.addSketchLayerButton = this.page.getByText("Add Sketch Layer");
this.layerNameInput = this.page.getByPlaceholder("Text");
this.createButton = this.page.getByRole("button").getByText("Create");
this.circleButton = this.page
.locator("div[direction='column'] > div[direction='row'] button")
.nth(2);
this.skyBoxSelectButton = this.page.locator(
"div:has(> div > p:text('Sky Box')) div:has(> p:text('Description needed.')) > div"
);
}

async goto() {
await this.page.goto(`/`);
}

async createNewLayerBySketch(name: string) {
await this.page.waitForSelector("button:text('New Layer')");
await this.newLayerButton.click();
await this.addSketchLayerButton.click();
await this.layerNameInput.fill(name);
await this.createButton.click();
}

async getLocatorOfCanvs() {
const box = await this.page.locator("canvas").boundingBox();
if (!box) throw new Error("no canvas found");
const x = box.x + box.width / 2;
const y = box.y + box.height / 2;
return { x, y };
}

async createCircleToEarthByLocator(x: number, y: number) {
await this.page.mouse.move(x, y);
await this.page.mouse.click(x, y, { button: "left" });
await this.page.waitForTimeout(5 * 1000);
await this.page.mouse.move(x + 20, y + 20);
await this.page.mouse.click(x + 20, y + 20, { button: "left", delay: 500 });
}

async closeSkyBox() {
await this.page.getByText("Sky").click();
await this.skyBoxSelectButton.click();
}
}
3 changes: 3 additions & 0 deletions web/e2e/pom/visualizer/editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import MapPage from "./MapPage";

export { MapPage };
5 changes: 5 additions & 0 deletions web/e2e/pom/visualizer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as dashboard from "./dashboard";
import * as editor from "./editor";
import * as projectSetting from "./projectSetting";

export { dashboard, editor, projectSetting };
Loading

0 comments on commit a798a4e

Please sign in to comment.