diff --git a/.dockerignore b/.dockerignore index 00f3070a..3c778957 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,5 @@ .vscode node_modules out +playwright-report +test-results diff --git a/.eslintignore b/.eslintignore index 62017156..77320ef9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,8 @@ .next node_modules out +playwright-report public scripts +test-results *.config.js diff --git a/.prettierignore b/.prettierignore index 0648e8bf..826025e0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,6 @@ .next node_modules out +playwright-report public +test-results diff --git a/components/system/Desktop/Wallpapers/useWallpaper.ts b/components/system/Desktop/Wallpapers/useWallpaper.ts index 4f1be6b7..e20fa1c3 100644 --- a/components/system/Desktop/Wallpapers/useWallpaper.ts +++ b/components/system/Desktop/Wallpapers/useWallpaper.ts @@ -38,6 +38,7 @@ import { declare global { interface Window { + DEBUG_DISABLE_WALLPAPER?: boolean; WallpaperDestroy?: () => void; } } @@ -388,7 +389,7 @@ const useWallpaper = ( ]); useEffect(() => { - if (sessionLoaded) { + if (sessionLoaded && !window.DEBUG_DISABLE_WALLPAPER) { if (wallpaperTimerRef.current) { window.clearTimeout(wallpaperTimerRef.current); } diff --git a/e2e/Accessibility.spec.ts b/e2e/Accessibility.spec.ts new file mode 100644 index 00000000..969c7c34 --- /dev/null +++ b/e2e/Accessibility.spec.ts @@ -0,0 +1,31 @@ +import AxeBuilder from "@axe-core/playwright"; +import { expect, test } from "@playwright/test"; +import { ACCESSIBILITY_EXCEPTION_IDS } from "e2e/constants"; +import { + canvasBackgroundIsVisible, + clockCanvasOrTextIsVisible, + clockIsVisible, + desktopEntriesAreVisible, + desktopIsVisible, + loadApp, + startButtonIsVisible, + taskbarIsVisible, +} from "e2e/functions"; + +test.beforeEach(loadApp); +test.beforeEach(desktopIsVisible); +test.beforeEach(desktopEntriesAreVisible); +test.beforeEach(taskbarIsVisible); +test.beforeEach(startButtonIsVisible); +test.beforeEach(clockIsVisible); +test.beforeEach(clockCanvasOrTextIsVisible); +test.beforeEach(canvasBackgroundIsVisible); + +test("pass accessibility scan", async ({ page }) => + expect( + ( + await new AxeBuilder({ page }) + .disableRules(ACCESSIBILITY_EXCEPTION_IDS) + .analyze() + ).violations + ).toEqual([])); diff --git a/e2e/components/apps/FileExplorer.spec.ts b/e2e/components/apps/FileExplorer.spec.ts index 73f72558..6be024eb 100644 --- a/e2e/components/apps/FileExplorer.spec.ts +++ b/e2e/components/apps/FileExplorer.spec.ts @@ -21,6 +21,7 @@ import { contextMenuEntryIsVisible, contextMenuIsVisible, desktopEntriesAreVisible, + disableWallpaper, fileExplorerAddressBarHasValue, fileExplorerEntriesAreVisible, fileExplorerEntryHasTooltip, @@ -33,12 +34,14 @@ import { windowsAreVisible, } from "e2e/functions"; +test.beforeEach(disableWallpaper); test.beforeEach(async ({ page }) => page.goto("/?app=FileExplorer")); test.beforeEach(windowsAreVisible); +test.beforeEach(fileExplorerEntriesAreVisible); test("has address bar", async ({ page }) => { await fileExplorerAddressBarHasValue(TEST_APP_TITLE, { page }); - await clickFileExplorerAddressBar({ page }); + await clickFileExplorerAddressBar({ page }, false, 2); await fileExplorerAddressBarHasValue("/", { page }); await clickFileExplorerAddressBar({ page }, true); @@ -57,7 +60,6 @@ test("has search box", async ({ page }) => { }); test.describe("has file(s)", () => { - test.beforeEach(fileExplorerEntriesAreVisible); test.beforeEach(async ({ page }) => clickFileExplorerEntry(TEST_ROOT_FILE, { page }) ); diff --git a/e2e/components/system/Apps.spec.ts b/e2e/components/system/Apps.spec.ts index 262224e2..550e8966 100644 --- a/e2e/components/system/Apps.spec.ts +++ b/e2e/components/system/Apps.spec.ts @@ -8,6 +8,7 @@ import { import { desktopEntriesAreVisible, desktopIsVisible, + disableWallpaper, dragFirstDesktopEntryToWindow, loadContainerTestApp, windowTitlebarIsVisible, @@ -15,6 +16,8 @@ import { windowsAreVisible, } from "e2e/functions"; +test.beforeEach(disableWallpaper); + test.describe("app container", () => { test.beforeEach(loadContainerTestApp); test.beforeEach(windowsAreVisible); diff --git a/e2e/components/system/Desktop.spec.ts b/e2e/components/system/Desktop.spec.ts index b9fa8db2..35e6a861 100644 --- a/e2e/components/system/Desktop.spec.ts +++ b/e2e/components/system/Desktop.spec.ts @@ -1,9 +1,6 @@ -import AxeBuilder from "@axe-core/playwright"; -import type { Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import type { IsShown } from "e2e/constants"; import { - ACCESSIBILITY_EXCEPTION_IDS, DESKTOP_MENU_ITEMS, DESKTOP_SELECTOR, NEW_FILE_LABEL, @@ -12,9 +9,6 @@ import { SELECTION_SELECTOR, } from "e2e/constants"; import { - backgroundIsUrl, - canvasBackgroundIsHidden, - canvasBackgroundIsVisible, clickContextMenuEntry, clickDesktop, contextMenuEntryIsHidden, @@ -24,26 +18,16 @@ import { desktopEntryIsHidden, desktopEntryIsVisible, desktopIsVisible, + disableWallpaper, loadApp, pressDesktopKeys, - taskbarEntriesAreVisible, - taskbarEntryIsVisible, + taskbarEntryIsOpen, } from "e2e/functions"; +test.beforeEach(disableWallpaper); test.beforeEach(loadApp); test.beforeEach(desktopIsVisible); -test("pass accessibility scan", async ({ page }) => - expect( - ( - await new AxeBuilder({ page }) - .disableRules(ACCESSIBILITY_EXCEPTION_IDS) - .analyze() - ).violations - ).toEqual([])); - -test("has background", canvasBackgroundIsVisible); - test("has file entry", desktopEntriesAreVisible); // TODO: has grid (move file on grid) @@ -78,14 +62,6 @@ test.describe("has selection", () => { // TODO: file entry (single/multi) }); -const taskbarEntriesOpened = async ( - label: RegExp, - page: Page -): Promise => { - await taskbarEntriesAreVisible({ page }); - await taskbarEntryIsVisible(label, { page }); -}; - test.describe("has context menu", () => { test.beforeEach(async ({ page }) => clickDesktop({ page }, true)); test.beforeEach(contextMenuIsVisible); @@ -157,47 +133,31 @@ test.describe("has context menu", () => { }); }); - test("can change background", async ({ page }) => { - await canvasBackgroundIsVisible({ page }); - - await clickContextMenuEntry(/^Background$/, { page }); - await clickContextMenuEntry(/^Picture Slideshow$/, { page }); - - await canvasBackgroundIsHidden({ page }); - await backgroundIsUrl({ page }); - - await page.reload(); - - await desktopIsVisible({ page }); - await canvasBackgroundIsHidden({ page }); - await backgroundIsUrl({ page }); - }); - test("can inspect", async ({ page }) => { await clickContextMenuEntry(/^Inspect$/, { page }); - await taskbarEntriesOpened(/^DevTools$/, page); + await taskbarEntryIsOpen(/^DevTools$/, page); }); test("can view page source", async ({ page }) => { await clickContextMenuEntry(/^View page source$/, { page }); - await taskbarEntriesOpened(/^index.html - Monaco Editor$/, page); + await taskbarEntryIsOpen(/^index.html - Monaco Editor$/, page); }); test("can open terminal", async ({ page }) => { await clickContextMenuEntry(/^Open Terminal here$/, { page }); - await taskbarEntriesOpened(/^Terminal$/, page); + await taskbarEntryIsOpen(/^Terminal$/, page); }); }); test.describe("has keyboard shortcuts", () => { test("ctrl + shift + r (open run dialog)", async ({ page }) => { await pressDesktopKeys("Control+Shift+KeyR", { page }); - await taskbarEntriesOpened(/^Run$/, page); + await taskbarEntryIsOpen(/^Run$/, page); }); test("ctrl + shift + e (open file explorer)", async ({ page }) => { await pressDesktopKeys("Control+Shift+KeyE", { page }); - await taskbarEntriesOpened(/^My PC$/, page); + await taskbarEntryIsOpen(/^My PC$/, page); }); // TODO: Ctrl+Shift+D diff --git a/e2e/components/system/StartMenu.spec.ts b/e2e/components/system/StartMenu.spec.ts index cb7f4a66..620f3410 100644 --- a/e2e/components/system/StartMenu.spec.ts +++ b/e2e/components/system/StartMenu.spec.ts @@ -2,6 +2,7 @@ import { test } from "@playwright/test"; import { clickDesktop, clickStartButton, + disableWallpaper, loadApp, startMenuEntryIsVisible, startMenuIsHidden, @@ -9,6 +10,7 @@ import { startMenuSidebarEntryIsVisible, } from "e2e/functions"; +test.beforeEach(disableWallpaper); test.beforeEach(loadApp); test.beforeEach(clickStartButton); test.beforeEach(startMenuIsVisible); diff --git a/e2e/components/system/Taskbar.spec.ts b/e2e/components/system/Taskbar.spec.ts index a506f5b6..13b36696 100644 --- a/e2e/components/system/Taskbar.spec.ts +++ b/e2e/components/system/Taskbar.spec.ts @@ -1,19 +1,15 @@ import { test } from "@playwright/test"; -import { - OFFSCREEN_CANVAS_NOT_SUPPORTED_BROWSERS, - TEST_APP_ICON, - TEST_APP_TITLE, -} from "e2e/constants"; +import { TEST_APP_ICON, TEST_APP_TITLE } from "e2e/constants"; import { calendarIsVisible, clickClock, clickStartButton, clockCanvasIsHidden, - clockCanvasIsVisible, + clockCanvasOrTextIsVisible, clockIsVisible, - clockTextIsHidden, clockTextIsVisible, disableOffscreenCanvas, + disableWallpaper, loadApp, loadTestApp, sheepIsVisible, @@ -25,6 +21,8 @@ import { taskbarIsVisible, } from "e2e/functions"; +test.beforeEach(disableWallpaper); + test.describe("elements", () => { test.beforeEach(loadApp); test.beforeEach(taskbarIsVisible); @@ -46,18 +44,10 @@ test.describe("elements", () => { test.describe("has clock", () => { test.beforeEach(clockIsVisible); - test("via canvas", async ({ browserName, page }) => { - if (OFFSCREEN_CANVAS_NOT_SUPPORTED_BROWSERS.has(browserName)) { - await clockTextIsVisible({ page }); - await clockCanvasIsHidden({ page }); - } else { - await clockTextIsHidden({ page }); - await clockCanvasIsVisible({ page }); - } - }); + test("via canvas", clockCanvasOrTextIsVisible); test("via text", async ({ page }) => { - await page.addInitScript(disableOffscreenCanvas); + await disableOffscreenCanvas({ page }); await page.reload(); await clockTextIsVisible({ page }); diff --git a/e2e/components/system/Wallpaper.spec.ts b/e2e/components/system/Wallpaper.spec.ts new file mode 100644 index 00000000..6ce49ef2 --- /dev/null +++ b/e2e/components/system/Wallpaper.spec.ts @@ -0,0 +1,35 @@ +import { test } from "@playwright/test"; +import { + backgroundIsUrl, + canvasBackgroundIsHidden, + canvasBackgroundIsVisible, + clickContextMenuEntry, + clickDesktop, + contextMenuIsVisible, + desktopIsVisible, + loadApp, +} from "e2e/functions"; + +test.beforeEach(loadApp); +test.beforeEach(desktopIsVisible); + +test("has background", canvasBackgroundIsVisible); + +test("can change background", async ({ page }) => { + await clickDesktop({ page }, true); + await contextMenuIsVisible({ page }); + + await canvasBackgroundIsVisible({ page }); + + await clickContextMenuEntry(/^Background$/, { page }); + await clickContextMenuEntry(/^Picture Slideshow$/, { page }); + + await canvasBackgroundIsHidden({ page }); + await backgroundIsUrl({ page }); + + await page.reload(); + + await desktopIsVisible({ page }); + await canvasBackgroundIsHidden({ page }); + await backgroundIsUrl({ page }); +}); diff --git a/e2e/components/system/Window.spec.ts b/e2e/components/system/Window.spec.ts index 9b1e9769..b2b1a463 100644 --- a/e2e/components/system/Window.spec.ts +++ b/e2e/components/system/Window.spec.ts @@ -8,6 +8,7 @@ import { clickCloseWindow, clickMaximizeWindow, clickMinimizeWindow, + disableWallpaper, doubleClickWindowTitlebar, doubleClickWindowTitlebarIcon, dragWindowToDesktop, @@ -21,9 +22,11 @@ import { windowsAreVisible, } from "e2e/functions"; +test.beforeEach(disableWallpaper); test.beforeEach(loadTestApp); // TODO: Check if window animation is indeed happening, and wait for it +// Q: Click titlebar to make sure it's focused and also for auto wait? Do in FE also. test.beforeEach(windowsAreVisible); test.beforeEach(windowTitlebarIsVisible); diff --git a/e2e/constants.ts b/e2e/constants.ts index 205231cc..6fc9fcb6 100644 --- a/e2e/constants.ts +++ b/e2e/constants.ts @@ -9,7 +9,6 @@ type LocatorWaitForProps = Parameters[0]; export const EXACT = { exact: true }; export const FORCE = { force: true }; -export const POLLING_OPTIONS = { timeout: 20000 }; export const RIGHT_CLICK = { button: "right" } as LocatorClickProps; export const VISIBLE = { state: "visible" } as LocatorWaitForProps; diff --git a/e2e/functions.ts b/e2e/functions.ts index 0ff60f6d..8c5a4689 100644 --- a/e2e/functions.ts +++ b/e2e/functions.ts @@ -14,7 +14,7 @@ import { FILE_EXPLORER_ENTRIES_SELECTOR, FILE_EXPLORER_NAV_SELECTOR, FILE_EXPLORER_SEARCH_BOX_LABEL, - POLLING_OPTIONS, + OFFSCREEN_CANVAS_NOT_SUPPORTED_BROWSERS, RIGHT_CLICK, SHEEP_SELECTOR, START_BUTTON_SELECTOR, @@ -33,10 +33,20 @@ type TestProps = { page: Page; }; -export const disableOffscreenCanvas = (): void => { - delete (window as Partial).OffscreenCanvas; +type TestPropsWithBrowser = TestProps & { + browserName: string; }; +export const disableOffscreenCanvas = ({ page }: TestProps): Promise => + page.addInitScript(() => { + delete (window as Partial).OffscreenCanvas; + }); + +export const disableWallpaper = ({ page }: TestProps): Promise => + page.addInitScript(() => { + window.DEBUG_DISABLE_WALLPAPER = true; + }); + // action export const loadApp = async ({ page }: TestProps): Promise => page.goto("/"); @@ -127,12 +137,16 @@ export const clickContextMenuEntry = async ( export const clickFileExplorerAddressBar = async ( { page }: TestProps, - right = false + right = false, + clickCount = 1 ): Promise => page .locator(FILE_EXPLORER_NAV_SELECTOR) .getByLabel(FILE_EXPLORER_ADDRESS_BAR_LABEL) - .click(right ? RIGHT_CLICK : undefined); + .click({ + button: right ? "right" : undefined, + clickCount, + }); export const clickFileExplorerEntry = async ( label: RegExp, @@ -189,25 +203,29 @@ export const backgroundIsUrl = async ({ page }: TestProps): Promise => .match(/^url\(.*?\)$/) ) ).toBeTruthy() - ).toPass(POLLING_OPTIONS); + ).toPass(); export const windowIsMaximized = async ({ page }: TestProps): Promise => expect(async () => expect( await page.evaluate( - ([windowSelector, taskbarSelector]) => - window.innerWidth === - (document.querySelector(windowSelector) as HTMLElement) - ?.clientWidth && - window.innerHeight - - ((document.querySelector(taskbarSelector) as HTMLElement) - ?.clientHeight || 0) === - (document.querySelector(windowSelector) as HTMLElement) - ?.clientHeight, + ([windowSelector, taskbarSelector]) => { + const { + clientWidth: windowWidth = 0, + clientHeight: windowHeight = 0, + } = document.querySelector(windowSelector) || {}; + const { clientHeight: taskbarHeight = 0 } = + document.querySelector(taskbarSelector) || {}; + + return ( + windowWidth === window.innerWidth && + windowHeight === window.innerHeight - taskbarHeight + ); + }, [WINDOW_SELECTOR, TASKBAR_SELECTOR] ) ).toBeTruthy() - ).toPass(POLLING_OPTIONS); + ).toPass(); // expect->locator export const canvasBackgroundIsHidden = async ({ @@ -407,7 +425,7 @@ export const taskbarEntryHasIcon = async ( const entriesAreVisible = async (selector: string, page: Page): Promise => expect(async () => expect(page.locator(selector).first()).toBeVisible() - ).toPass(POLLING_OPTIONS); + ).toPass(); export const desktopEntriesAreVisible = async ({ page, @@ -426,3 +444,25 @@ export const taskbarEntriesAreVisible = async ({ export const windowsAreVisible = async ({ page }: TestProps): Promise => entriesAreVisible(WINDOW_SELECTOR, page); + +// meta function +export const clockCanvasOrTextIsVisible = async ({ + browserName, + page, +}: TestPropsWithBrowser): Promise => { + if (OFFSCREEN_CANVAS_NOT_SUPPORTED_BROWSERS.has(browserName)) { + await clockTextIsVisible({ page }); + await clockCanvasIsHidden({ page }); + } else { + await clockTextIsHidden({ page }); + await clockCanvasIsVisible({ page }); + } +}; + +export const taskbarEntryIsOpen = async ( + label: RegExp, + page: Page +): Promise => { + await taskbarEntriesAreVisible({ page }); + await taskbarEntryIsVisible(label, { page }); +}; diff --git a/playwright.config.ts b/playwright.config.ts index 27ee3a8c..ceea44c5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -26,6 +26,8 @@ const config: PlaywrightTestConfig = { testDir: "e2e", use: { baseURL, + trace: process.env.CI ? "off" : "retain-on-failure", + video: process.env.CI ? "off" : "retain-on-failure", }, webServer: { command: "yarn dev",