mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-04 05:36:13 +08:00 
			
		
		
		
	Playwright + Native Node Test Runner (#3893)
This commit is contained in:
		
							
								
								
									
										17
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								.eslintrc.js
									
									
									
									
									
								
							@@ -150,23 +150,6 @@ module.exports = {
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // Override for jest puppeteer
 | 
			
		||||
        {
 | 
			
		||||
            "files": [
 | 
			
		||||
                "**/*.spec.js",
 | 
			
		||||
                "**/*.spec.jsx"
 | 
			
		||||
            ],
 | 
			
		||||
            env: {
 | 
			
		||||
                jest: true,
 | 
			
		||||
            },
 | 
			
		||||
            globals: {
 | 
			
		||||
                page: true,
 | 
			
		||||
                browser: true,
 | 
			
		||||
                context: true,
 | 
			
		||||
                jestPuppeteer: true,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // Override for TypeScript
 | 
			
		||||
        {
 | 
			
		||||
            "files": [
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								.github/workflows/auto-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								.github/workflows/auto-test.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,7 +15,7 @@ on:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  auto-test:
 | 
			
		||||
    needs: [ check-linters ]
 | 
			
		||||
    needs: [ check-linters, e2e-test ]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    timeout-minutes: 15
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +36,7 @@ jobs:
 | 
			
		||||
    - run: npm install npm@9 -g
 | 
			
		||||
    - run: npm install
 | 
			
		||||
    - run: npm run build
 | 
			
		||||
    - run: npm test
 | 
			
		||||
    - run: npm run test-backend
 | 
			
		||||
      env:
 | 
			
		||||
        HEADLESS_TEST: 1
 | 
			
		||||
        JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
 | 
			
		||||
@@ -78,33 +78,18 @@ jobs:
 | 
			
		||||
    - run: npm install
 | 
			
		||||
    - run: npm run lint:prod
 | 
			
		||||
 | 
			
		||||
# TODO: Temporarily disable, as it cannot pass the test in 2.0.0 yet
 | 
			
		||||
#  e2e-tests:
 | 
			
		||||
#    needs: [ check-linters ]
 | 
			
		||||
#    runs-on: ubuntu-latest
 | 
			
		||||
#    steps:
 | 
			
		||||
#    - run: git config --global core.autocrlf false  # Mainly for Windows
 | 
			
		||||
#    - uses: actions/checkout@v4
 | 
			
		||||
#
 | 
			
		||||
#    - name: Use Node.js 14
 | 
			
		||||
#      uses: actions/setup-node@v4
 | 
			
		||||
#      with:
 | 
			
		||||
#        node-version: 14
 | 
			
		||||
#    - run: npm install
 | 
			
		||||
#    - run: npm run build
 | 
			
		||||
#    - run: npm run cy:test
 | 
			
		||||
 | 
			
		||||
  frontend-unit-tests:
 | 
			
		||||
  e2e-test:
 | 
			
		||||
    needs: [ check-linters ]
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ARM64
 | 
			
		||||
    steps:
 | 
			
		||||
    - run: git config --global core.autocrlf false  # Mainly for Windows
 | 
			
		||||
    - uses: actions/checkout@v4
 | 
			
		||||
 | 
			
		||||
    - name: Use Node.js 14
 | 
			
		||||
    - name: Use Node.js 20
 | 
			
		||||
      uses: actions/setup-node@v4
 | 
			
		||||
      with:
 | 
			
		||||
        node-version: 14
 | 
			
		||||
        node-version: 20
 | 
			
		||||
    - run: npm install
 | 
			
		||||
    - run: npx playwright install
 | 
			
		||||
    - run: npm run build
 | 
			
		||||
    - run: npm run cy:run:unit
 | 
			
		||||
    - run: npm run test-e2e
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -15,9 +15,6 @@ dist-ssr
 | 
			
		||||
/tmp
 | 
			
		||||
.env
 | 
			
		||||
 | 
			
		||||
cypress/videos
 | 
			
		||||
cypress/screenshots
 | 
			
		||||
 | 
			
		||||
/extra/healthcheck.exe
 | 
			
		||||
/extra/healthcheck
 | 
			
		||||
/extra/healthcheck-armv7
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								config/playwright.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								config/playwright.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import { defineConfig, devices } from "@playwright/test";
 | 
			
		||||
 | 
			
		||||
const port = 30001;
 | 
			
		||||
const url = `http://localhost:${port}`;
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
    // Look for test files in the "tests" directory, relative to this configuration file.
 | 
			
		||||
    testDir: "../test/e2e",
 | 
			
		||||
    outputDir: "../private/playwright-test-results",
 | 
			
		||||
    fullyParallel: false,
 | 
			
		||||
    locale: "en-US",
 | 
			
		||||
 | 
			
		||||
    // Fail the build on CI if you accidentally left test.only in the source code.
 | 
			
		||||
    forbidOnly: !!process.env.CI,
 | 
			
		||||
 | 
			
		||||
    // Retry on CI only.
 | 
			
		||||
    retries: process.env.CI ? 2 : 0,
 | 
			
		||||
 | 
			
		||||
    // Opt out of parallel tests on CI.
 | 
			
		||||
    workers: 1,
 | 
			
		||||
 | 
			
		||||
    // Reporter to use
 | 
			
		||||
    reporter: [
 | 
			
		||||
        [
 | 
			
		||||
            "html", {
 | 
			
		||||
                outputFolder: "../private/playwright-report",
 | 
			
		||||
                open: "never",
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    use: {
 | 
			
		||||
        // Base URL to use in actions like `await page.goto('/')`.
 | 
			
		||||
        baseURL: url,
 | 
			
		||||
 | 
			
		||||
        // Collect trace when retrying the failed test.
 | 
			
		||||
        trace: "on-first-retry",
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Configure projects for major browsers.
 | 
			
		||||
    projects: [
 | 
			
		||||
        {
 | 
			
		||||
            name: "chromium",
 | 
			
		||||
            use: { ...devices["Desktop Chrome"] },
 | 
			
		||||
        },
 | 
			
		||||
        /*
 | 
			
		||||
        {
 | 
			
		||||
            name: "firefox",
 | 
			
		||||
            use: { browserName: "firefox" }
 | 
			
		||||
        },*/
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    // Run your local dev server before starting the tests.
 | 
			
		||||
    webServer: {
 | 
			
		||||
        command: `node extra/remove-playwright-test-data.js && node server/server.js --port=${port} --data-dir=./data/playwright-test`,
 | 
			
		||||
        url,
 | 
			
		||||
        reuseExistingServer: false,
 | 
			
		||||
        cwd: "../",
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										6
									
								
								extra/remove-playwright-test-data.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								extra/remove-playwright-test-data.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
 | 
			
		||||
fs.rmSync("./data/playwright-test", {
 | 
			
		||||
    recursive: true,
 | 
			
		||||
    force: true,
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										5984
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5984
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										22
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								package.json
									
									
									
									
									
								
							@@ -25,12 +25,15 @@
 | 
			
		||||
        "start-server-dev": "cross-env NODE_ENV=development node server/server.js",
 | 
			
		||||
        "start-server-dev:watch": "cross-env NODE_ENV=development node --watch server/server.js",
 | 
			
		||||
        "build": "vite build --config ./config/vite.config.js",
 | 
			
		||||
        "test": "node test/prepare-test-server.js && npm run test-backend",
 | 
			
		||||
        "test": "npm run test-backend && npm run test-e2e",
 | 
			
		||||
        "test-with-build": "npm run build && npm test",
 | 
			
		||||
        "test-backend": "node test/backend-test-entry.js && npm run jest-backend",
 | 
			
		||||
        "test-backend": "node test/backend-test-entry.js",
 | 
			
		||||
        "test-backend:14": "cross-env TEST_BACKEND=1 NODE_OPTIONS=\"--experimental-abortcontroller --no-warnings\" node--test test/backend-test",
 | 
			
		||||
        "test-backend:18": "cross-env TEST_BACKEND=1 node --test test/backend-test",
 | 
			
		||||
        "jest-backend": "cross-env TEST_BACKEND=1 jest --runInBand --detectOpenHandles --forceExit --config=./config/jest-backend.config.js",
 | 
			
		||||
        "test-e2e": "playwright test --config ./config/playwright.config.js",
 | 
			
		||||
        "test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
 | 
			
		||||
        "playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
 | 
			
		||||
        "playwright-show-report": "playwright show-report ./private/playwright-report",
 | 
			
		||||
        "tsc": "tsc",
 | 
			
		||||
        "vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
 | 
			
		||||
        "build-docker": "npm run build && npm run build-docker-full && npm run build-docker-slim",
 | 
			
		||||
@@ -62,10 +65,6 @@
 | 
			
		||||
        "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",
 | 
			
		||||
        "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 --config-file ./config/cypress.config.js",
 | 
			
		||||
        "cy:run:unit": "npx cypress run --browser chrome --headless --config-file ./config/cypress.frontend.config.js",
 | 
			
		||||
        "cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
 | 
			
		||||
        "build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
 | 
			
		||||
        "deploy-demo-server": "node extra/deploy-demo-server.js",
 | 
			
		||||
        "sort-contributors": "node extra/sort-contributors.js",
 | 
			
		||||
@@ -98,8 +97,8 @@
 | 
			
		||||
        "express-static-gzip": "~2.1.7",
 | 
			
		||||
        "form-data": "~4.0.0",
 | 
			
		||||
        "gamedig": "^4.2.0",
 | 
			
		||||
        "http-cookie-agent": "~5.0.4",
 | 
			
		||||
        "html-escaper": "^3.0.3",
 | 
			
		||||
        "http-cookie-agent": "~5.0.4",
 | 
			
		||||
        "http-graceful-shutdown": "~3.1.7",
 | 
			
		||||
        "http-proxy-agent": "~5.0.0",
 | 
			
		||||
        "https-proxy-agent": "~5.0.1",
 | 
			
		||||
@@ -128,7 +127,7 @@
 | 
			
		||||
        "password-hash": "~1.2.2",
 | 
			
		||||
        "pg": "~8.11.3",
 | 
			
		||||
        "pg-connection-string": "~2.6.2",
 | 
			
		||||
        "playwright-core": "~1.35.1",
 | 
			
		||||
        "playwright-core": "~1.39.0",
 | 
			
		||||
        "prom-client": "~13.2.0",
 | 
			
		||||
        "prometheus-api-metrics": "~3.2.1",
 | 
			
		||||
        "promisify-child-process": "~4.1.2",
 | 
			
		||||
@@ -152,8 +151,10 @@
 | 
			
		||||
        "@fortawesome/free-regular-svg-icons": "~5.15.4",
 | 
			
		||||
        "@fortawesome/free-solid-svg-icons": "~5.15.4",
 | 
			
		||||
        "@fortawesome/vue-fontawesome": "~3.0.0-5",
 | 
			
		||||
        "@playwright/test": "~1.39.0",
 | 
			
		||||
        "@popperjs/core": "~2.10.2",
 | 
			
		||||
        "@types/bootstrap": "~5.1.9",
 | 
			
		||||
        "@types/node": "^20.8.6",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^6.7.5",
 | 
			
		||||
        "@typescript-eslint/parser": "^6.7.5",
 | 
			
		||||
        "@vitejs/plugin-vue": "~4.2.3",
 | 
			
		||||
@@ -167,7 +168,6 @@
 | 
			
		||||
        "core-js": "~3.26.1",
 | 
			
		||||
        "cronstrue": "~2.24.0",
 | 
			
		||||
        "cross-env": "~7.0.3",
 | 
			
		||||
        "cypress": "^13.2.0",
 | 
			
		||||
        "delay": "^5.0.0",
 | 
			
		||||
        "dns2": "~2.0.1",
 | 
			
		||||
        "dompurify": "~2.4.3",
 | 
			
		||||
@@ -175,7 +175,7 @@
 | 
			
		||||
        "eslint-plugin-jsdoc": "~46.4.6",
 | 
			
		||||
        "eslint-plugin-vue": "~8.7.1",
 | 
			
		||||
        "favico.js": "~0.3.10",
 | 
			
		||||
        "jest": "~29.6.1",
 | 
			
		||||
        "get-port-please": "^3.1.1",
 | 
			
		||||
        "marked": "~4.2.5",
 | 
			
		||||
        "node-ssh": "~13.1.0",
 | 
			
		||||
        "postcss-html": "~1.5.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ const Monitor = require("./model/monitor");
 | 
			
		||||
const User = require("./model/user");
 | 
			
		||||
 | 
			
		||||
log.debug("server", "Importing Settings");
 | 
			
		||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, startE2eTests, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
 | 
			
		||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
 | 
			
		||||
} = require("./util-server");
 | 
			
		||||
 | 
			
		||||
log.debug("server", "Importing Notification");
 | 
			
		||||
@@ -130,7 +130,6 @@ const twoFAVerifyOptions = {
 | 
			
		||||
 * @type {boolean}
 | 
			
		||||
 */
 | 
			
		||||
const testMode = !!args["test"] || false;
 | 
			
		||||
const e2eTestMode = !!args["e2e"] || false;
 | 
			
		||||
 | 
			
		||||
// Must be after io instantiation
 | 
			
		||||
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList, sendRemoteBrowserList } = require("./client");
 | 
			
		||||
@@ -1550,10 +1549,6 @@ let needSetup = false;
 | 
			
		||||
        }
 | 
			
		||||
        startMonitors();
 | 
			
		||||
        checkVersion.startInterval();
 | 
			
		||||
 | 
			
		||||
        if (e2eTestMode) {
 | 
			
		||||
            startE2eTests();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await initBackgroundJobs();
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ const { R } = require("redbean-node");
 | 
			
		||||
const { log, genSecret, badgeConstants } = require("../src/util");
 | 
			
		||||
const passwordHash = require("./password-hash");
 | 
			
		||||
const { Resolver } = require("dns");
 | 
			
		||||
const childProcess = require("child_process");
 | 
			
		||||
const iconv = require("iconv-lite");
 | 
			
		||||
const chardet = require("chardet");
 | 
			
		||||
const chroma = require("chroma-js");
 | 
			
		||||
@@ -797,29 +796,6 @@ exports.doubleCheckPassword = async (socket, currentPassword) => {
 | 
			
		||||
    return user;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Start end-to-end tests
 | 
			
		||||
 * @returns {void}
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,323 +0,0 @@
 | 
			
		||||
// ⚠️⚠️⚠️ Deprecated: Jest is not recommended for testing backend code anymore, please create a new test file in ./test/backend-test which are native Node.js test files.
 | 
			
		||||
 | 
			
		||||
const { genSecret, DOWN, log} = require("../src/util");
 | 
			
		||||
const utilServer = require("../server/util-server");
 | 
			
		||||
const Discord = require("../server/notification-providers/discord");
 | 
			
		||||
const axios = require("axios");
 | 
			
		||||
const { UptimeKumaServer } = require("../server/uptime-kuma-server");
 | 
			
		||||
const Database = require("../server/database");
 | 
			
		||||
const {Settings} = require("../server/settings");
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
const dayjs = require("dayjs");
 | 
			
		||||
dayjs.extend(require("dayjs/plugin/utc"));
 | 
			
		||||
dayjs.extend(require("dayjs/plugin/timezone"));
 | 
			
		||||
 | 
			
		||||
jest.mock("axios");
 | 
			
		||||
 | 
			
		||||
describe("Test parseCertificateInfo", () => {
 | 
			
		||||
    it("should handle undefined", async () => {
 | 
			
		||||
        const parseCertificateInfo = utilServer.__getPrivateFunction("parseCertificateInfo");
 | 
			
		||||
        const info = parseCertificateInfo(undefined);
 | 
			
		||||
        expect(info).toEqual(undefined);
 | 
			
		||||
    }, 5000);
 | 
			
		||||
 | 
			
		||||
    it("should handle normal cert chain", async () => {
 | 
			
		||||
        const parseCertificateInfo = utilServer.__getPrivateFunction("parseCertificateInfo");
 | 
			
		||||
 | 
			
		||||
        const chain1 = {
 | 
			
		||||
            fingerprint: "CF:2C:F3:6A:FE:6B:10:EC:44:77:C8:95:BB:96:2E:06:1F:0E:15:DA",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain2 = {
 | 
			
		||||
            fingerprint: "A0:31:C4:67:82:E6:E6:C6:62:C2:C8:7C:76:DA:9A:A6:2C:CA:BD:8E",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain3 = {
 | 
			
		||||
            fingerprint: "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        chain1.issuerCertificate = chain2;
 | 
			
		||||
        chain2.issuerCertificate = chain3;
 | 
			
		||||
        chain3.issuerCertificate = chain3;
 | 
			
		||||
 | 
			
		||||
        const info = parseCertificateInfo(chain1);
 | 
			
		||||
        expect(chain1).toEqual(info);
 | 
			
		||||
    }, 5000);
 | 
			
		||||
 | 
			
		||||
    it("should handle cert chain with strange circle", async () => {
 | 
			
		||||
        const parseCertificateInfo = utilServer.__getPrivateFunction("parseCertificateInfo");
 | 
			
		||||
 | 
			
		||||
        const chain1 = {
 | 
			
		||||
            fingerprint: "CF:2C:F3:6A:FE:6B:10:EC:44:77:C8:95:BB:96:2E:06:1F:0E:15:DA",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain2 = {
 | 
			
		||||
            fingerprint: "A0:31:C4:67:82:E6:E6:C6:62:C2:C8:7C:76:DA:9A:A6:2C:CA:BD:8E",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain3 = {
 | 
			
		||||
            fingerprint: "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain4 = {
 | 
			
		||||
            fingerprint: "haha",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        chain1.issuerCertificate = chain2;
 | 
			
		||||
        chain2.issuerCertificate = chain3;
 | 
			
		||||
        chain3.issuerCertificate = chain4;
 | 
			
		||||
        chain4.issuerCertificate = chain2;
 | 
			
		||||
 | 
			
		||||
        const info = parseCertificateInfo(chain1);
 | 
			
		||||
        expect(chain1).toEqual(info);
 | 
			
		||||
    }, 5000);
 | 
			
		||||
 | 
			
		||||
    it("should handle cert chain with last undefined (should be happen in real, but just in case)", async () => {
 | 
			
		||||
        const parseCertificateInfo = utilServer.__getPrivateFunction("parseCertificateInfo");
 | 
			
		||||
 | 
			
		||||
        const chain1 = {
 | 
			
		||||
            fingerprint: "CF:2C:F3:6A:FE:6B:10:EC:44:77:C8:95:BB:96:2E:06:1F:0E:15:DA",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain2 = {
 | 
			
		||||
            fingerprint: "A0:31:C4:67:82:E6:E6:C6:62:C2:C8:7C:76:DA:9A:A6:2C:CA:BD:8E",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain3 = {
 | 
			
		||||
            fingerprint: "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const chain4 = {
 | 
			
		||||
            fingerprint: "haha",
 | 
			
		||||
            valid_from: "Oct 22 12:00:00 2013 GMT",
 | 
			
		||||
            valid_to: "Oct 22 12:00:00 2028 GMT",
 | 
			
		||||
            subjectaltname: "DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        chain1.issuerCertificate = chain2;
 | 
			
		||||
        chain2.issuerCertificate = chain3;
 | 
			
		||||
        chain3.issuerCertificate = chain4;
 | 
			
		||||
        chain4.issuerCertificate = undefined;
 | 
			
		||||
 | 
			
		||||
        const info = parseCertificateInfo(chain1);
 | 
			
		||||
        expect(chain1).toEqual(info);
 | 
			
		||||
    }, 5000);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Test genSecret", () => {
 | 
			
		||||
 | 
			
		||||
    beforeAll(() => {
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should be correct length", () => {
 | 
			
		||||
        let secret = genSecret(-1);
 | 
			
		||||
        expect(secret).toEqual("");
 | 
			
		||||
 | 
			
		||||
        secret = genSecret(0);
 | 
			
		||||
        expect(secret).toEqual("");
 | 
			
		||||
 | 
			
		||||
        secret = genSecret(1);
 | 
			
		||||
        expect(secret.length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
        secret = genSecret(2);
 | 
			
		||||
        expect(secret.length).toEqual(2);
 | 
			
		||||
 | 
			
		||||
        secret = genSecret(64);
 | 
			
		||||
        expect(secret.length).toEqual(64);
 | 
			
		||||
 | 
			
		||||
        secret = genSecret(9000);
 | 
			
		||||
        expect(secret.length).toEqual(9000);
 | 
			
		||||
 | 
			
		||||
        secret = genSecret(90000);
 | 
			
		||||
        expect(secret.length).toEqual(90000);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should contain first and last possible chars", () => {
 | 
			
		||||
        let secret = genSecret(90000);
 | 
			
		||||
        expect(secret).toContain("A");
 | 
			
		||||
        expect(secret).toContain("9");
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Test reset-password", () => {
 | 
			
		||||
    it("should able to run", async () => {
 | 
			
		||||
        await require("../extra/reset-password").main();
 | 
			
		||||
    }, 120000);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Test Discord Notification Provider", () => {
 | 
			
		||||
    const hostname = "discord.com";
 | 
			
		||||
    const port = 1337;
 | 
			
		||||
 | 
			
		||||
    const sendNotification = async (hostname, port, type) => {
 | 
			
		||||
        const discordProvider = new Discord();
 | 
			
		||||
 | 
			
		||||
        axios.post.mockResolvedValue({});
 | 
			
		||||
 | 
			
		||||
        await discordProvider.send(
 | 
			
		||||
            {
 | 
			
		||||
                discordUsername: "Uptime Kuma",
 | 
			
		||||
                discordWebhookUrl: "https://discord.com",
 | 
			
		||||
            },
 | 
			
		||||
            "test message",
 | 
			
		||||
            {
 | 
			
		||||
                type,
 | 
			
		||||
                hostname,
 | 
			
		||||
                port,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                status: DOWN,
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    it("should send hostname for ping monitors", async () => {
 | 
			
		||||
        await sendNotification(hostname, null, "ping");
 | 
			
		||||
        expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(hostname);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([ "dns", "port", "steam" ])("should send hostname for %p monitors", async (type) => {
 | 
			
		||||
        await sendNotification(hostname, port, type);
 | 
			
		||||
        expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(`${hostname}:${port}`);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("The function filterAndJoin", () => {
 | 
			
		||||
    it("should join and array of strings to one string", () => {
 | 
			
		||||
        const result = utilServer.filterAndJoin([ "one", "two", "three" ]);
 | 
			
		||||
        expect(result).toBe("onetwothree");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should join strings using a given connector", () => {
 | 
			
		||||
        const result = utilServer.filterAndJoin([ "one", "two", "three" ], "-");
 | 
			
		||||
        expect(result).toBe("one-two-three");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should filter null, undefined and empty strings before joining", () => {
 | 
			
		||||
        const result = utilServer.filterAndJoin([ undefined, "", "three" ], "--");
 | 
			
		||||
        expect(result).toBe("three");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should return an empty string if all parts are filtered out", () => {
 | 
			
		||||
        const result = utilServer.filterAndJoin([ undefined, "", "" ], "--");
 | 
			
		||||
        expect(result).toBe("");
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Test uptimeKumaServer.getClientIP()", () => {
 | 
			
		||||
    it("should able to get a correct client IP", async () => {
 | 
			
		||||
        Database.initDataDir({
 | 
			
		||||
            "data-dir": "./data/test"
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (! fs.existsSync(Database.sqlitePath)) {
 | 
			
		||||
            log.info("server", "Copying Database");
 | 
			
		||||
            fs.copyFileSync(Database.templatePath, Database.sqlitePath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Database.connect(true);
 | 
			
		||||
        await Database.patch();
 | 
			
		||||
 | 
			
		||||
        const fakeSocket = {
 | 
			
		||||
            client: {
 | 
			
		||||
                conn: {
 | 
			
		||||
                    remoteAddress: "192.168.10.10",
 | 
			
		||||
                    request: {
 | 
			
		||||
                        headers: {
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const server = Object.create(UptimeKumaServer.prototype);
 | 
			
		||||
        let ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
 | 
			
		||||
        await Settings.set("trustProxy", false);
 | 
			
		||||
        expect(await Settings.get("trustProxy")).toBe(false);
 | 
			
		||||
        expect(ip).toBe("192.168.10.10");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-forwarded-for"] = "10.10.10.10";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("192.168.10.10");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-real-ip"] = "20.20.20.20";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("192.168.10.10");
 | 
			
		||||
 | 
			
		||||
        await Settings.set("trustProxy", true);
 | 
			
		||||
        expect(await Settings.get("trustProxy")).toBe(true);
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-forwarded-for"] = "10.10.10.10";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("10.10.10.10");
 | 
			
		||||
 | 
			
		||||
        // x-real-ip
 | 
			
		||||
        delete fakeSocket.client.conn.request.headers["x-forwarded-for"];
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("20.20.20.20");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-forwarded-for"] = "2001:db8:85a3:8d3:1319:8a2e:370:7348";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("2001:db8:85a3:8d3:1319:8a2e:370:7348");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("203.0.113.195");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("203.0.113.195");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195,2001:db8:85a3:8d3:1319:8a2e:370:7348,150.172.238.178";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("203.0.113.195");
 | 
			
		||||
 | 
			
		||||
        // Elements are comma-separated, with optional whitespace surrounding the commas.
 | 
			
		||||
        fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195 , 2001:db8:85a3:8d3:1319:8a2e:370:7348,150.172.238.178";
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("203.0.113.195");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.remoteAddress = "2001:db8::1";
 | 
			
		||||
        fakeSocket.client.conn.request.headers = {};
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("2001:db8::1");
 | 
			
		||||
 | 
			
		||||
        fakeSocket.client.conn.remoteAddress = "::ffff:127.0.0.1";
 | 
			
		||||
        fakeSocket.client.conn.request.headers = {};
 | 
			
		||||
        ip = await server.getClientIP(fakeSocket);
 | 
			
		||||
        expect(ip).toBe("127.0.0.1");
 | 
			
		||||
 | 
			
		||||
        await Database.close();
 | 
			
		||||
    }, 120000);
 | 
			
		||||
});
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
const actor = require("../support/actors/actor");
 | 
			
		||||
const userData = require("../support/const/user-data");
 | 
			
		||||
const dashboardPage = require("../support/pages/dashboard-page");
 | 
			
		||||
const setupPage = require("../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.SetupPage.url);
 | 
			
		||||
        actor.actor.setupTask.fillAndSubmitSetupForm(userData.DEFAULT_USER_DATA.username, userData.DEFAULT_USER_DATA.password, userData.DEFAULT_USER_DATA.password);
 | 
			
		||||
        cy.url().should("be.equal", dashboardPage.DashboardPage.url);
 | 
			
		||||
        cy.get('[role="alert"]')
 | 
			
		||||
            .should("be.visible")
 | 
			
		||||
            .and("contain.text", "Added Successfully.");
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
const setupTask = require("../tasks/setup-task");
 | 
			
		||||
class Actor {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.setupTask = new setupTask.SetupTask();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
const actor = new Actor();
 | 
			
		||||
exports.actor = actor;
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
exports.DEFAULT_USER_DATA = {
 | 
			
		||||
    username: "testuser",
 | 
			
		||||
    password: "testuser123",
 | 
			
		||||
};
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
require("./commands");
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
exports.DashboardPage = {
 | 
			
		||||
    url: Cypress.env("baseUrl") + "/dashboard",
 | 
			
		||||
};
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
exports.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"]',
 | 
			
		||||
};
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
const setupPage = require("../pages/setup-page");
 | 
			
		||||
 | 
			
		||||
class SetupTask {
 | 
			
		||||
    fillAndSubmitSetupForm(username, password, passwordRepeat) {
 | 
			
		||||
        cy.get(setupPage.SetupPage.usernameInput).type(username);
 | 
			
		||||
        cy.get(setupPage.SetupPage.passWordInput).type(password);
 | 
			
		||||
        cy.get(setupPage.SetupPage.passwordRepeatInput).type(passwordRepeat);
 | 
			
		||||
        cy.get(setupPage.SetupPage.submitSetupForm).click();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
exports.SetupTask = SetupTask;
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
import { currentLocale } from "../../../src/i18n";
 | 
			
		||||
 | 
			
		||||
describe("Test i18n.js", () => {
 | 
			
		||||
 | 
			
		||||
    it("currentLocale()", () => {
 | 
			
		||||
        const setLanguage = (language) => {
 | 
			
		||||
            Object.defineProperty(window.navigator, 'language', { 
 | 
			
		||||
                value: language, 
 | 
			
		||||
                writable: true 
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        setLanguage('en-EN');
 | 
			
		||||
 | 
			
		||||
        expect(currentLocale()).equal("en");
 | 
			
		||||
 | 
			
		||||
        setLanguage('zh-HK');
 | 
			
		||||
        expect(currentLocale()).equal("zh-HK");
 | 
			
		||||
 | 
			
		||||
        // Note that in Safari on iOS prior to 10.2, the country code returned is lowercase: "en-us", "fr-fr" etc.
 | 
			
		||||
        // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language
 | 
			
		||||
        setLanguage('zh-hk');
 | 
			
		||||
        expect(currentLocale()).equal("en");
 | 
			
		||||
 | 
			
		||||
        setLanguage('en-US');
 | 
			
		||||
        expect(currentLocale()).equal("en");
 | 
			
		||||
 | 
			
		||||
        setLanguage('ja-ZZ');
 | 
			
		||||
        expect(currentLocale()).equal("ja");
 | 
			
		||||
 | 
			
		||||
        setLanguage('zz-ZZ');
 | 
			
		||||
        expect(currentLocale()).equal("en");
 | 
			
		||||
 | 
			
		||||
        setLanguage('zz-ZZ');
 | 
			
		||||
        expect(currentLocale()).equal("en");
 | 
			
		||||
 | 
			
		||||
        setLanguage('en');
 | 
			
		||||
        localStorage.locale = "en";
 | 
			
		||||
        expect(currentLocale()).equal("en");
 | 
			
		||||
 | 
			
		||||
        localStorage.locale = "zh-HK";
 | 
			
		||||
        expect(currentLocale()).equal("zh-HK");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
import { hostNameRegexPattern } from "../../../src/util-frontend";
 | 
			
		||||
 | 
			
		||||
describe("Test util-frontend.js", () => {
 | 
			
		||||
 | 
			
		||||
    describe("hostNameRegexPattern()", () => {
 | 
			
		||||
        it('should return a valid regex for non mqtt hostnames', () => {
 | 
			
		||||
            const regex = new RegExp(hostNameRegexPattern(false));
 | 
			
		||||
 | 
			
		||||
            expect(regex.test("www.test.com")).to.be.true;
 | 
			
		||||
            expect(regex.test("127.0.0.1")).to.be.true;
 | 
			
		||||
            expect(regex.test("192.168.1.156")).to.be.true;
 | 
			
		||||
            expect(regex.test(" 192.168.1.145")).to.be.false;
 | 
			
		||||
            expect(regex.test("192.168.1.145 ")).to.be.false;
 | 
			
		||||
            expect(regex.test(" fe80::3282:3ff:ae28:592")).to.be.false;
 | 
			
		||||
            expect(regex.test("fe80::3282:3ff:ae28:592 ")).to.be.false;
 | 
			
		||||
 | 
			
		||||
            ["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
 | 
			
		||||
                expect(regex.test(`${schema}://www.test.com`)).to.be.false;
 | 
			
		||||
                expect(regex.test(`${schema}://127.0.0.1`)).to.be.false;
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        it('should return a valid regex for mqtt hostnames', () => {
 | 
			
		||||
            const hostnameString = hostNameRegexPattern(false);
 | 
			
		||||
            console.log('*********', hostnameString, '***********');
 | 
			
		||||
            const regex = new RegExp(hostNameRegexPattern(true));
 | 
			
		||||
 | 
			
		||||
            expect(regex.test("www.test.com")).to.be.true;
 | 
			
		||||
            expect(regex.test("127.0.0.1")).to.be.true;
 | 
			
		||||
            expect(regex.test("192.168.1.156")).to.be.true;
 | 
			
		||||
            expect(regex.test(" 192.168.1.145")).to.be.false;
 | 
			
		||||
            expect(regex.test("192.168.1.145 ")).to.be.false;
 | 
			
		||||
            expect(regex.test(" fe80::3282:3ff:ae28:592")).to.be.false;
 | 
			
		||||
            expect(regex.test("fe80::3282:3ff:ae28:592 ")).to.be.false;
 | 
			
		||||
 | 
			
		||||
            ["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
 | 
			
		||||
                expect(regex.test(`${schema}://www.test.com`)).to.be.true;
 | 
			
		||||
                expect(regex.test(`${schema}://127.0.0.1`)).to.be.true;
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										43
									
								
								test/e2e/setup.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								test/e2e/setup.spec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
import { test } from "@playwright/test";
 | 
			
		||||
import { login, screenshot } from "./util-test";
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Setup
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
test("setup sqlite", async ({ page }, testInfo) => {
 | 
			
		||||
    await page.goto("./");
 | 
			
		||||
    await page.getByText("SQLite").click();
 | 
			
		||||
    await page.getByRole("button", { name: "Next" }).click();
 | 
			
		||||
    await screenshot(testInfo, page);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("setup admin", async ({ page }, testInfo) => {
 | 
			
		||||
    await page.goto("./");
 | 
			
		||||
    await page.getByPlaceholder("Username").click();
 | 
			
		||||
    await page.getByPlaceholder("Username").fill("admin");
 | 
			
		||||
    await page.getByPlaceholder("Username").press("Tab");
 | 
			
		||||
    await page.getByPlaceholder("Password", { exact: true }).fill("admin123");
 | 
			
		||||
    await page.getByPlaceholder("Password", { exact: true }).press("Tab");
 | 
			
		||||
    await page.getByPlaceholder("Repeat Password").fill("admin123");
 | 
			
		||||
    await page.getByRole("button", { name: "Create" }).click();
 | 
			
		||||
    await screenshot(testInfo, page);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * All other tests should be run after setup
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
test("login", async ({ page }, testInfo) => {
 | 
			
		||||
    await page.goto("./dashboard");
 | 
			
		||||
    await login(page);
 | 
			
		||||
    await screenshot(testInfo, page);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("logout", async ({ page }, testInfo) => {
 | 
			
		||||
    await page.goto("./dashboard");
 | 
			
		||||
    await login(page);
 | 
			
		||||
    await page.getByText("A", { exact: true }).click();
 | 
			
		||||
    await page.getByRole("button", { name: "Logout" }).click();
 | 
			
		||||
    await screenshot(testInfo, page);
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										27
									
								
								test/e2e/util-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								test/e2e/util-test.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @param {TestInfo} testInfo Test info
 | 
			
		||||
 * @param {Page} page Page
 | 
			
		||||
 * @returns {Promise<void>}
 | 
			
		||||
 */
 | 
			
		||||
export async function screenshot(testInfo, page) {
 | 
			
		||||
    const screenshot = await page.screenshot();
 | 
			
		||||
    await testInfo.attach("screenshot", {
 | 
			
		||||
        body: screenshot,
 | 
			
		||||
        contentType: "image/png"
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Page} page Page
 | 
			
		||||
 * @returns {Promise<void>}
 | 
			
		||||
 */
 | 
			
		||||
export async function login(page) {
 | 
			
		||||
    // Login
 | 
			
		||||
    await page.getByPlaceholder("Username").click();
 | 
			
		||||
    await page.getByPlaceholder("Username").fill("admin");
 | 
			
		||||
    await page.getByPlaceholder("Username").press("Tab");
 | 
			
		||||
    await page.getByPlaceholder("Password").fill("admin123");
 | 
			
		||||
    await page.getByLabel("Remember me").check();
 | 
			
		||||
    await page.getByRole("button", { name: "Login" }).click();
 | 
			
		||||
    await page.isVisible("text=Add New Monitor");
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user