diff --git a/.dockerignore b/.dockerignore index 4ce0e13a..babc429a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ /.idea /node_modules /data +/cypress /out /test /kubernetes diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index d26b76e6..b49a253c 100644 --- a/.github/workflows/auto-test.yml +++ b/.github/workflows/auto-test.yml @@ -50,3 +50,19 @@ jobs: cache: 'npm' - run: npm install - run: npm run lint + + e2e-tests: + needs: [ check-linters ] + runs-on: ubuntu-latest + steps: + - run: git config --global core.autocrlf false # Mainly for Windows + - uses: actions/checkout@v3 + + - name: Use Node.js 14 + uses: actions/setup-node@v3 + with: + node-version: 14 + cache: 'npm' + - run: npm install + - run: npm run build + - run: npm run cy:test diff --git a/.gitignore b/.gitignore index cd654d90..8eb05867 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ dist-ssr /out /tmp .env + +cypress/videos +cypress/screenshots diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000..d97e0875 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + baseUrl: "http://localhost:3002", + defaultCommandTimeout: 10000, + pageLoadTimeout: 60000, + viewportWidth: 1920, + viewportHeight: 1080, + specPattern: ["cypress/e2e/setup.cy.ts", "cypress/e2e/**/*.ts"], + }, + env: { + baseUrl: "http://localhost:3002", + }, +}); diff --git a/cypress/e2e/setup.cy.ts b/cypress/e2e/setup.cy.ts new file mode 100644 index 00000000..94e18ede --- /dev/null +++ b/cypress/e2e/setup.cy.ts @@ -0,0 +1,24 @@ +import { actor } from "../support/actors/actor"; +import { DEFAULT_USER_DATA } from "../support/const/user-data"; +import { DashboardPage } from "../support/pages/dasboard-page"; +import { SetupPage } from "../support/pages/setup-page"; + +describe("user can create a new account on setup page", () => { + before(() => { + cy.visit("/setup"); + }); + + it("user can create new account", () => { + cy.url().should("be.equal", SetupPage.url); + actor.setupTask.fillAndSubmitSetupForm( + DEFAULT_USER_DATA.username, + DEFAULT_USER_DATA.password, + DEFAULT_USER_DATA.password + ); + + cy.url().should("be.equal", DashboardPage.url); + cy.get('[role="alert"]') + .should("be.visible") + .and("contain.text", "Added Successfully."); + }); +}); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 00000000..e69de29b diff --git a/cypress/support/actors/actor.ts b/cypress/support/actors/actor.ts new file mode 100644 index 00000000..680c26ce --- /dev/null +++ b/cypress/support/actors/actor.ts @@ -0,0 +1,8 @@ +import { SetupTask } from "../tasks/setup-task"; + +class Actor { + setupTask: SetupTask = new SetupTask(); +} + +const actor = new Actor(); +export { actor }; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 00000000..e69de29b diff --git a/cypress/support/const/user-data.ts b/cypress/support/const/user-data.ts new file mode 100644 index 00000000..ee2264dd --- /dev/null +++ b/cypress/support/const/user-data.ts @@ -0,0 +1,4 @@ +export const DEFAULT_USER_DATA = { + username: "testuser", + password: "testuser123", +}; diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 00000000..f887c29a --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1 @@ +import "./commands"; diff --git a/cypress/support/pages/dasboard-page.ts b/cypress/support/pages/dasboard-page.ts new file mode 100644 index 00000000..48660dc1 --- /dev/null +++ b/cypress/support/pages/dasboard-page.ts @@ -0,0 +1,3 @@ +export const DashboardPage = { + url: Cypress.env("baseUrl") + "/dashboard", +}; diff --git a/cypress/support/pages/setup-page.ts b/cypress/support/pages/setup-page.ts new file mode 100644 index 00000000..8c1b9cfa --- /dev/null +++ b/cypress/support/pages/setup-page.ts @@ -0,0 +1,7 @@ +export const SetupPage = { + url: Cypress.env("baseUrl") + "/setup", + usernameInput: '[data-cy="username-input"]', + passWordInput: '[data-cy="password-input"]', + passwordRepeatInput: '[data-cy="password-repeat-input"]', + submitSetupForm: '[data-cy="submit-setup-form"]', +}; diff --git a/cypress/support/tasks/setup-task.ts b/cypress/support/tasks/setup-task.ts new file mode 100644 index 00000000..866e3ca5 --- /dev/null +++ b/cypress/support/tasks/setup-task.ts @@ -0,0 +1,15 @@ +import { SetupPage } from "../pages/setup-page"; + +export class SetupTask { + fillAndSubmitSetupForm( + username: string, + password: string, + passwordRepeat: string + ) { + cy.get(SetupPage.usernameInput).type(username); + cy.get(SetupPage.passWordInput).type(password); + cy.get(SetupPage.passwordRepeatInput).type(passwordRepeat); + + cy.get(SetupPage.submitSetupForm).click(); + } +} diff --git a/package.json b/package.json index 86341c24..be335ae0 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,9 @@ "release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts", "git-remove-tag": "git tag -d", "build-dist-and-restart": "npm run build && npm run start-server-dev", - "start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev" + "start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev", + "cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e", + "cy:run": "npx cypress run --browser chrome --headless" }, "dependencies": { "@louislam/sqlite3": "~15.0.6", @@ -130,6 +132,8 @@ "concurrently": "^7.1.0", "core-js": "~3.18.3", "cross-env": "~7.0.3", + "cypress": "^10.1.0", + "delay": "^5.0.0", "dns2": "~2.0.1", "eslint": "~8.14.0", "eslint-plugin-vue": "~8.7.1", diff --git a/server/server.js b/server/server.js index 818bd7d1..0c9a45e6 100644 --- a/server/server.js +++ b/server/server.js @@ -61,7 +61,7 @@ log.info("server", "Importing this project modules"); log.debug("server", "Importing Monitor"); const Monitor = require("./model/monitor"); log.debug("server", "Importing Settings"); -const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, doubleCheckPassword } = require("./util-server"); +const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, doubleCheckPassword, startE2eTests } = require("./util-server"); log.debug("server", "Importing Notification"); const { Notification } = require("./notification"); @@ -112,6 +112,7 @@ const twoFAVerifyOptions = { * @type {boolean} */ const testMode = !!args["test"] || false; +const e2eTestMode = !!args["e2e"] || false; if (config.demoMode) { log.info("server", "==== Demo Mode ===="); @@ -1486,6 +1487,10 @@ let needSetup = false; if (testMode) { startUnitTest(); } + + if (e2eTestMode) { + startE2eTests(); + } }); initBackgroundJobs(args); diff --git a/server/util-server.js b/server/util-server.js index 067da6fd..1517bcfe 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -573,6 +573,26 @@ exports.startUnitTest = async () => { }); }; +/** Start end-to-end tests */ +exports.startE2eTests = async () => { + console.log("Starting unit test..."); + const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; + const child = childProcess.spawn(npm, [ "run", "cy:run" ]); + + child.stdout.on("data", (data) => { + console.log(data.toString()); + }); + + child.stderr.on("data", (data) => { + console.log(data.toString()); + }); + + child.on("close", function (code) { + console.log("Jest exit code: " + code); + process.exit(code); + }); +}; + /** * Convert unknown string to UTF8 * @param {Uint8Array} body Buffer diff --git a/src/pages/Setup.vue b/src/pages/Setup.vue index 08347b8e..cba7f8fc 100644 --- a/src/pages/Setup.vue +++ b/src/pages/Setup.vue @@ -1,5 +1,5 @@