chore(e2e): updates testing to use playwright (#2181)
* chore(e2e): updates testing to use playwright * chore: updates workflows * fixes spaces * fixes space again * fixes int test to add docker * chore: ignore e2e tests for vite * updates screenshots for linux * updates screenshots again * chore: uses docker compose for e2e * adds PWTEST_SKIP_TEST_OUTPUT * add ci * updates screenshots again * updates with css * adds more tests * updates tests
3
e2e/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
videos
|
||||
screenshots
|
||||
__diff_output__
|
||||
@@ -1,11 +0,0 @@
|
||||
FROM cypress/included:12.12.0
|
||||
|
||||
RUN npm i -g pnpm
|
||||
|
||||
WORKDIR /e2e
|
||||
|
||||
COPY pnpm-lock.yaml ./
|
||||
RUN pnpm fetch
|
||||
|
||||
COPY package.json tsconfig.json ./
|
||||
RUN pnpm install --offline
|
||||
9
e2e/auth.spec.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("authentication", async ({ page }) => {
|
||||
await page.goto("http://auth:8080/");
|
||||
await page.locator('input[name="username"]').fill("foo");
|
||||
await page.locator('input[name="password"]').fill("bar");
|
||||
await page.getByRole("button", { name: "Login" }).click();
|
||||
await expect(page.locator("p.menu-label")).toHaveText("Containers");
|
||||
});
|
||||
13
e2e/custom.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("http://custom_base:8080/foobarbase");
|
||||
});
|
||||
|
||||
test("has right title", async ({ page }) => {
|
||||
await expect(page).toHaveTitle(/.* - Dozzle/);
|
||||
});
|
||||
|
||||
test("url should have custom base", async ({ page }) => {
|
||||
await expect(page).toHaveURL(/foobarbase/);
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { defineConfig } from "cypress";
|
||||
import { initPlugin } from "@frsource/cypress-plugin-visual-regression-diff/dist/plugins";
|
||||
|
||||
export default defineConfig({
|
||||
fixturesFolder: false,
|
||||
projectId: "8cua4m",
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
initPlugin(on, config);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"DOZZLE_DEFAULT": "http://localhost:8080/",
|
||||
"DOZZLE_AUTH": "http://localhost:8080/"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB |
@@ -1,14 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
context("Dozzle default mode", { baseUrl: Cypress.env("DOZZLE_AUTH") }, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/");
|
||||
});
|
||||
|
||||
it("login screen", () => {
|
||||
cy.get("input[name=username]").type("foo");
|
||||
cy.get("input[name=password]").type("bar");
|
||||
cy.get("button[type=submit]").click();
|
||||
cy.get("p.menu-label").should("contain", "Containers");
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
context("Dozzle default mode", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/");
|
||||
});
|
||||
|
||||
it("home screen", () => {
|
||||
cy.get("li.running", { timeout: 10000 }).removeDates().replaceSkippedElements().matchImage();
|
||||
});
|
||||
|
||||
it("correct title is shown", () => {
|
||||
cy.title().should("eq", "1 containers - Dozzle");
|
||||
|
||||
cy.get("li.running:first a").click();
|
||||
|
||||
cy.title().should("include", "- Dozzle");
|
||||
});
|
||||
|
||||
it("navigating to setting page works ", () => {
|
||||
cy.get("a[href='/settings']").click();
|
||||
|
||||
cy.contains("About");
|
||||
});
|
||||
|
||||
it("shortcut for fuzzy search works", () => {
|
||||
cy.get("body").type("{ctrl}k");
|
||||
|
||||
cy.get("input[placeholder='Search containers (⌘ + k, ⌃k)']").should("be.visible");
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
context("Dozzle es lang", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/", {
|
||||
onBeforeLoad(win) {
|
||||
Object.defineProperty(win.navigator, "language", {
|
||||
value: "es_MX",
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should find contenedores", () => {
|
||||
cy.get("p.menu-label").should("contain", "Contenedores");
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
context("Dozzle settings mode", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/version").clearLocalStorage().visit("/settings");
|
||||
});
|
||||
|
||||
it("scrollbars", () => {
|
||||
cy.contains("Use smaller scrollbars").click();
|
||||
cy.get("html").should("have.class", "has-custom-scrollbars");
|
||||
});
|
||||
|
||||
it("stopped containers", () => {
|
||||
cy.contains("Show stopped containers")
|
||||
.click()
|
||||
.then(() => {
|
||||
expect(JSON.parse(localStorage.getItem("DOZZLE_SETTINGS")).showAllContainers).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
context("Dozzle custom base", { baseUrl: Cypress.env("DOZZLE_CUSTOM") }, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/");
|
||||
});
|
||||
|
||||
it("custom base should work", () => {
|
||||
cy.get("p.menu-label").should("contain", "Containers");
|
||||
});
|
||||
|
||||
it("url should be custom", () => {
|
||||
cy.url().should("include", "foobarbase");
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
context("Dozzle dark mode", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/");
|
||||
cy.window().then((win) => win.document.documentElement.setAttribute("data-theme", "dark"));
|
||||
});
|
||||
|
||||
it("home screen", () => {
|
||||
cy.get("li.running", { timeout: 10000 }).removeDates().replaceSkippedElements().matchImage();
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
context("Dozzle routes", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () => {
|
||||
it("show", () => {
|
||||
cy.visit("/show?name=dozzle").url().should("include", "/container/");
|
||||
});
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,45 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Cypress.Commands.add("removeDates", () => {
|
||||
cy.window().then((win) => win.document.querySelectorAll("time").forEach((el) => el.remove()));
|
||||
});
|
||||
|
||||
Cypress.Commands.add("replaceSkippedElements", () => {
|
||||
cy.window().then((win) => win.document.querySelectorAll("[data-ci-skip]").forEach((el) => el.remove()));
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "./commands";
|
||||
import "@frsource/cypress-plugin-visual-regression-diff/dist/support";
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
// be explicit about types included
|
||||
// to avoid clashing with Jest types
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": [
|
||||
"../node_modules/cypress",
|
||||
"./**/*.ts",
|
||||
"./**/*.js"
|
||||
]
|
||||
}
|
||||
32
e2e/default.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("http://dozzle:8080/");
|
||||
});
|
||||
|
||||
test("has right title", async ({ page }) => {
|
||||
await expect(page).toHaveTitle(/.* - Dozzle/);
|
||||
});
|
||||
|
||||
test("click on settings button", async ({ page }) => {
|
||||
await page.getByRole("link", { name: "Settings" }).click();
|
||||
await expect(page.getByRole("heading", { name: "About" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("shortcut for fuzzy search", async ({ page }) => {
|
||||
await page.locator("body").press("Control+k");
|
||||
await expect(page.locator(".modal").getByPlaceholder("Search containers (⌘ + k, ⌃k)")).toBeVisible();
|
||||
});
|
||||
|
||||
test("route by name", async ({ page }) => {
|
||||
await page.goto("http://dozzle:8080/show?name=dozzle");
|
||||
await expect(page).toHaveURL(/\/container/);
|
||||
});
|
||||
|
||||
test.describe("es locale", () => {
|
||||
test.use({ locale: "es" });
|
||||
|
||||
test("translated text", async ({ page }) => {
|
||||
await expect(page.locator("p.menu-label").getByText("Contenedores")).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
version: "3.4"
|
||||
services:
|
||||
custom_base:
|
||||
container_name: custom_base
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- DOZZLE_FILTER=name=custom_base
|
||||
- DOZZLE_BASE=/foobarbase
|
||||
- DOZZLE_NO_ANALYTICS=1
|
||||
image: amir20/dozzle_custom_cache
|
||||
build:
|
||||
context: ..
|
||||
cache_from:
|
||||
- amir20/dozzle_custom_cache:latest
|
||||
auth:
|
||||
container_name: auth
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- DOZZLE_FILTER=name=auth
|
||||
- DOZZLE_USERNAME=foo
|
||||
- DOZZLE_PASSWORD=bar
|
||||
- DOZZLE_NO_ANALYTICS=1
|
||||
image: amir20/dozzle_custom_cache
|
||||
build:
|
||||
context: ..
|
||||
cache_from:
|
||||
- amir20/dozzle_custom_cache:latest
|
||||
dozzle:
|
||||
container_name: dozzle
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- DOZZLE_FILTER=name=dozzle
|
||||
- DOZZLE_NO_ANALYTICS=1
|
||||
image: amir20/dozzle_cache:latest
|
||||
build:
|
||||
context: ..
|
||||
cache_from:
|
||||
- amir20/dozzle_cache:latest
|
||||
cypress:
|
||||
build:
|
||||
context: .
|
||||
cache_from:
|
||||
- amir20/dozzle_cypress_cache:latest
|
||||
image: amir20/dozzle_cypress_cache:latest
|
||||
working_dir: /e2e
|
||||
volumes:
|
||||
- ./cypress:/e2e/cypress
|
||||
- ./cypress.config.ts:/e2e/cypress.config.ts
|
||||
environment:
|
||||
- CYPRESS_DOZZLE_DEFAULT=http://dozzle:8080/
|
||||
- CYPRESS_DOZZLE_AUTH=http://auth:8080/
|
||||
- CYPRESS_DOZZLE_CUSTOM=http://custom_base:8080/foobarbase
|
||||
- CYPRESS_RECORD_KEY=155c3cf8-b2dd-4f5e-9fb3-7635f5b79d4d
|
||||
- COMMIT_INFO_BRANCH=${GITHUB_REF_NAME}
|
||||
- COMMIT_INFO_AUTHOR=${GITHUB_ACTOR}
|
||||
- COMMIT_INFO_SHA=${GITHUB_SHA}
|
||||
- COMMIT_INFO_MESSAGE=${GIT_LOG_MESSAGE}
|
||||
- COMMIT_INFO_REMOTE=https://github.com/amir20/dozzle
|
||||
command: cypress run --record
|
||||
depends_on:
|
||||
- dozzle
|
||||
- custom_base
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "e2e",
|
||||
"scripts": {
|
||||
"test": "cypress run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@frsource/cypress-plugin-visual-regression-diff": "^3.3.0",
|
||||
"cypress": "^12.12.0",
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
}
|
||||
1414
e2e/pnpm-lock.yaml
generated
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
20
e2e/visual.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("http://dozzle:8080/");
|
||||
});
|
||||
|
||||
test.describe("default", () => {
|
||||
test("homepage", async ({ page }) => {
|
||||
await page.addStyleTag({ content: `[data-ci-skip] { visibility: hidden; }` });
|
||||
await expect(page).toHaveScreenshot({});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("dark", () => {
|
||||
test.use({ colorScheme: "dark" });
|
||||
test("homepage", async ({ page }) => {
|
||||
await page.addStyleTag({ content: `[data-ci-skip] { visibility: hidden; }` });
|
||||
await expect(page).toHaveScreenshot({});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 17 KiB |
BIN
e2e/visual.spec.ts-snapshots/dark-homepage-1-chromium-linux.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
e2e/visual.spec.ts-snapshots/dark-homepage-1-webkit-linux.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 29 KiB |
BIN
e2e/visual.spec.ts-snapshots/default-homepage-1-webkit-linux.png
Normal file
|
After Width: | Height: | Size: 26 KiB |