From 81d4f01b7f3d534772df4e51a03f3fc0515703ea Mon Sep 17 00:00:00 2001 From: CorrectRoadH Date: Tue, 11 Apr 2023 22:13:06 +0800 Subject: [PATCH] feat: add e2e test (#1486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add i18n * add base e2e test * add multiple test for e2e * extract the funciton of write memo * change test sturct * deteled unused dir * use fixture * add fixture * restruced the project * feat: add workflow * feat: change playwright test position * feat: change playwright test position * using yarn intead of npm * change install method * only enable sign in test * adjust the order of test * change report pos * fix style of e2e workflow * add review test * unify locale * randome write content * change report pos * reduce unused wait time * reduce unused folder * stash * merge upstream locale * change test name * add test item * change action name * add lanuage setting * add shotscreen * change name of test * fix the error of import dep * fix the error of import dep * fix the error of filename * fix the format of workflow * fix the name error of test case * feat: change the describe of test case * feat: remove unused test * feat: change the fixtures name * feat: remove unused config * feat: change docker action * feat: change the generate method * feat: extrace screenshot * feat: change extra path * feat: change extra path * feat: screenshot and upload * feat: change upload filename * feat: change login method * feat: change e2e method * feat: change e2e test * feat: add wait for login --------- Co-authored-by: CorrectRoadH --- .github/workflows/e2e-test.yml | 62 +++++++++++++++++++++++++++++++++ web/.gitignore | 4 +++ web/e2e-tests/001-setup.spec.ts | 13 +++++++ web/e2e-tests/002-basic.spec.ts | 37 ++++++++++++++++++++ web/e2e-tests/fixtures.ts | 2 ++ web/e2e-tests/utils.ts | 52 +++++++++++++++++++++++++++ web/package.json | 2 ++ web/playwright.config.ts | 19 ++++++++++ web/yarn.lock | 40 ++++++++++++++++++++- 9 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/e2e-test.yml create mode 100644 web/e2e-tests/001-setup.spec.ts create mode 100644 web/e2e-tests/002-basic.spec.ts create mode 100644 web/e2e-tests/fixtures.ts create mode 100644 web/e2e-tests/utils.ts create mode 100644 web/playwright.config.ts diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml new file mode 100644 index 00000000..dfbb52e4 --- /dev/null +++ b/.github/workflows/e2e-test.yml @@ -0,0 +1,62 @@ +name: "E2E Test" +on: + push: + branches: [main] + pull_request: + branches: [main] +jobs: + build: + name: Build and Run Memos With E2E Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Build Docker image + id: docker_build + uses: docker/build-push-action@v3 + with: + context: ./ + file: ./Dockerfile + platforms: linux/amd64 + push: false + tags: neosmemo/memos:e2e + labels: neosmemo/memos:e2e + + - name: Run Docker container + run: docker run -d -p 5230:5230 neosmemo/memos:e2e + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'yarn' + cache-dependency-path: ./web/yarn.lock + + - name: Install dependencies + working-directory: web + run: yarn + + - name: Install Playwright Browsers + working-directory: web + run: npx playwright install --with-deps + + - name: Run Playwright tests + working-directory: web + run: npx playwright test + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: web/playwright-report/ + retention-days: 30 + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-screenshot + path: web/playwright-screenshot/ + retention-days: 30 + + - name: Stop Docker container + run: docker stop $(docker ps -q) diff --git a/web/.gitignore b/web/.gitignore index f791607e..908acf7b 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -5,3 +5,7 @@ dist-ssr *.local .yarn/* +/test-results/ +/playwright-report/ +/playwright/.cache/ +/playwright-screenshot/ \ No newline at end of file diff --git a/web/e2e-tests/001-setup.spec.ts b/web/e2e-tests/001-setup.spec.ts new file mode 100644 index 00000000..b7e05017 --- /dev/null +++ b/web/e2e-tests/001-setup.spec.ts @@ -0,0 +1,13 @@ +import { test } from "@playwright/test"; +import { signUp } from "./utils"; + +test.use({ + locale: "en-US", + timezoneId: "Europe/Berlin", +}); + +test.describe("Sign up a host account", async () => { + test("Sign Up", async ({ page }) => { + await signUp(page, "admin", "admin"); + }); +}); diff --git a/web/e2e-tests/002-basic.spec.ts b/web/e2e-tests/002-basic.spec.ts new file mode 100644 index 00000000..c82ab684 --- /dev/null +++ b/web/e2e-tests/002-basic.spec.ts @@ -0,0 +1,37 @@ +import { test, expect } from "@playwright/test"; +import { review, login, writeMemo } from "./utils"; +import randomstring from "randomstring"; + +test.use({ + locale: "en-US", + timezoneId: "Europe/Berlin", +}); + +test.beforeEach(async ({ page }) => { + await login(page, "admin", "admin"); +}); + +test.describe("Write some memos", async () => { + test("Write memos", async ({ page }) => { + const content = `${randomstring.generate()} from Write memos`; + await writeMemo(page, content); + await expect(page.getByText(content)).toBeVisible(); + }); + + test("Write memos with Tag", async ({ page }) => { + const tag = randomstring.generate(5); + const content = `#${tag} ${randomstring.generate()} from Write memos with Tag`; + await writeMemo(page, content); + // 1.memo contentg 2.tags list of memos editor 3.tags list + await expect(page.getByText(tag)).toHaveCount(3); + }); +}); + +test.describe("Daily Review", async () => { + test("Daily Review", async ({ page }) => { + const content = randomstring.generate(); + await writeMemo(page, content); + await review(page); + await expect(page.getByText(content)).toBeVisible(); + }); +}); diff --git a/web/e2e-tests/fixtures.ts b/web/e2e-tests/fixtures.ts new file mode 100644 index 00000000..2713e151 --- /dev/null +++ b/web/e2e-tests/fixtures.ts @@ -0,0 +1,2 @@ +const baseHost = "http://localhost:5230"; +export { baseHost }; diff --git a/web/e2e-tests/utils.ts b/web/e2e-tests/utils.ts new file mode 100644 index 00000000..c8ff0f4f --- /dev/null +++ b/web/e2e-tests/utils.ts @@ -0,0 +1,52 @@ +import { expect, Page } from "@playwright/test"; +import locale from "../src/locales/en.json"; +import { baseHost } from "./fixtures"; + +async function screenshot(page: Page, name: string) { + await page.screenshot({ path: `playwright-screenshot/${name}.png`, fullPage: true }); +} + +async function writeMemo(page: Page, content: string) { + await expect(page.getByRole("button", { name: locale.editor.save })).toBeDisabled(); + await page.getByPlaceholder("Any thoughts...").fill(content); + await expect(page.getByRole("button", { name: locale.editor.save })).toBeEnabled(); + await page.getByRole("button", { name: locale.editor.save }).click(); +} + +async function login(page: Page, username: string, password: string) { + page.goto(`${baseHost}/`); + await screenshot(page, "explore-page"); + await page.waitForURL("**/explore"); + await screenshot(page, "explore-page-after-wait"); + await page.getByRole("link", { name: locale.common["sign-in"] }).click(); + await screenshot(page, "auth-page"); + await page.waitForURL("**/auth"); + await page.locator('input[type="text"]').click(); + await page.locator('input[type="text"]').fill(username); + await page.locator('input[type="password"]').click(); + await page.locator('input[type="password"]').fill(password); + await page.getByRole("button", { name: locale.common["sign-in"] }).click(); + await page.waitForTimeout(1000); + await screenshot(page, "home-page-login-success"); +} + +async function signUp(page: Page, username: string, password: string) { + await page.goto(`${baseHost}/`); + await page.waitForURL("**/auth"); + await screenshot(page, "sign-up-page"); + await page.locator('input[type="text"]').click(); + await page.locator('input[type="text"]').fill(username); + await page.locator('input[type="password"]').click(); + await page.locator('input[type="password"]').fill(password); + await page.getByRole("button", { name: locale.auth["signup-as-host"] }).click(); + await page.waitForTimeout(1000); + await screenshot(page, "home-page-sign-up-success"); +} + +async function review(page: Page) { + await page.goto(`${baseHost}/`); + await page.getByRole("link", { name: locale["daily-review"]["title"] }).click(); + await screenshot(page, "review"); +} + +export { writeMemo, login, signUp, review }; diff --git a/web/package.json b/web/package.json index e4ff5676..fd8c0418 100644 --- a/web/package.json +++ b/web/package.json @@ -22,6 +22,7 @@ "lucide-react": "^0.105.0", "qrcode.react": "^3.1.0", "qs": "^6.11.0", + "randomstring": "^1.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.0", @@ -34,6 +35,7 @@ "zustand": "^4.3.6" }, "devDependencies": { + "@playwright/test": "^1.32.2", "@tailwindcss/line-clamp": "^0.4.2", "@types/lodash-es": "^4.17.5", "@types/node": "^18.0.3", diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 00000000..619c7075 --- /dev/null +++ b/web/playwright.config.ts @@ -0,0 +1,19 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./e2e-tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: [["html", { outputFolder: "playwright-report", open: "never" }]], + use: { + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/web/yarn.lock b/web/yarn.lock index 10cccc4c..309bc54a 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -487,6 +487,16 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@playwright/test@^1.32.2": + version "1.32.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.32.2.tgz#3cbd76b3f94d0f7f50bf054dbd02e504e85e3865" + integrity sha512-nhaTSDpEdTTttdkDE8Z6K3icuG1DVRxrl98Qq0Lfc63SS9a2sjc9+x8ezysh7MzCKz6Y+nArml3/mmt+gqRmQQ== + dependencies: + "@types/node" "*" + playwright-core "1.32.2" + optionalDependencies: + fsevents "2.3.2" + "@popperjs/core@^2.11.6": version "2.11.6" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" @@ -787,6 +797,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== +"@types/node@*": + version "18.15.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + "@types/node@^18.0.3": version "18.11.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" @@ -1032,6 +1047,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-uniq@1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.2.tgz#5fcc373920775723cfd64d65c64bef53bf9eba6d" + integrity sha512-GVYjmpL05al4dNlKJm53mKE4w9OOLiuVHWorsIA3YVz+Hu0hcn6PtE3Ydl0EqU7v+7ABC4mjjWsnLUxbpno+CA== + array.prototype.flatmap@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" @@ -1785,7 +1805,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: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -2595,6 +2615,11 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +playwright-core@1.32.2: + version "1.32.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.32.2.tgz#608810c3c4486fb86a224732ac0d3560a96ded8b" + integrity sha512-zD7aonO+07kOTthsrCR3YCVnDcqSHIJpdFUtZEMOb6//1Rc7/6mZDRdw+nlzcQiQltOOsiqI3rrSyn/SlyjnJQ== + postcss-import@^14.1.0: version "14.1.0" resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" @@ -2706,6 +2731,19 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +randombytes@2.0.3: + version "2.0.3" + resolved "https://registry.npmmirror.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" + integrity sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg== + +randomstring@^1.2.3: + version "1.2.3" + resolved "https://registry.npmmirror.com/randomstring/-/randomstring-1.2.3.tgz#49d2bc34ff6bc2bd0f6bb8e7d876e1d4433564c8" + integrity sha512-3dEFySepTzp2CvH6W/ASYGguPPveBuz5MpZ7MuoUkoVehmyNl9+F9c9GFVrz2QPbM9NXTIHGcmJDY/3j4677kQ== + dependencies: + array-uniq "1.0.2" + randombytes "2.0.3" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"