diff --git a/.husky/post-install b/.husky/post-install new file mode 100644 index 00000000..9efbbc29 --- /dev/null +++ b/.husky/post-install @@ -0,0 +1 @@ +npx playwright install diff --git a/package.json b/package.json index 39a35721..7efa3b54 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,14 @@ "@angular/localize": "^17", "@commitlint/cli": "^19.2.1", "@commitlint/config-conventional": "^19.1.0", - "@playwright/test": "^1.36.0", + "@istanbuljs/nyc-config-typescript": "^1.0.2", "@nx/angular": "18.3.4", "@nx/eslint": "18.3.4", "@nx/eslint-plugin": "18.3.4", "@nx/jest": "18.3.4", "@nx/storybook": "18.3.4", "@nxext/stencil": "^18", + "@playwright/test": "^1.36.0", "@stencil/angular-output-target": "^0.8.4", "@stencil/core": "^4.15.0", "@stencil/sass": "^3.0.11", @@ -41,10 +42,14 @@ "karma-jasmine-html-reporter": "^2.1.0", "ng-packagr": "^17", "nx": "18.3.4", + "nyc": "^17.1.0", "prettier": "^3.2.5", "pretty-quick": "^4.0.0", + "source-map-support": "^0.5.21", "ts-jest": "^29", - "tsx": "^4.7.3" + "ts-node": "^10.9.2", + "tsx": "^4.7.3", + "v8-to-istanbul": "^9.3.0" }, "dependencies": { "@angular/animations": "^17", @@ -63,6 +68,7 @@ "jszip": "^3.10.1", "mime": "^4.0.1", "ngx-toastr": "^18.0.0", + "root": "file:", "shepherd.js": "^11.2.0", "soundswallower": "^0.6.3", "standardized-audio-context": "^25.3.70", diff --git a/packages/studio-web/.nycrc b/packages/studio-web/.nycrc new file mode 100644 index 00000000..21348854 --- /dev/null +++ b/packages/studio-web/.nycrc @@ -0,0 +1,15 @@ +{ + "extends": "@istanbuljs/nyc-config-typescript", + "all": false, + "include": ["src/app/*.ts"], + "exclude": [ + "**/*spec.ts", + "**/*config.ts", + "**/*lang.ts", + "src/@types/audio-recorder-polyfill/mpeg-encoder/index.d.ts" + ], + "reporter": ["html", "text-summary"], + "sourceMap": true, + "instrument": true, + "require": ["ts-node/register"] +} diff --git a/packages/studio-web/package.json b/packages/studio-web/package.json index cda13d2d..c2163300 100644 --- a/packages/studio-web/package.json +++ b/packages/studio-web/package.json @@ -11,6 +11,7 @@ "test:ng": "ng test", "test:once": "ng test --watch=false --browsers ChromeHeadlessCI", "e2e": "playwright test", + "nyc": "nyc --reporter=html --reporter=text-summary playwright test ", "e2e-ui": "playwright test --ui" }, "private": true, diff --git a/packages/studio-web/playwright.config.ts b/packages/studio-web/playwright.config.ts index d18cd0e7..2b1046e8 100644 --- a/packages/studio-web/playwright.config.ts +++ b/packages/studio-web/playwright.config.ts @@ -12,14 +12,14 @@ import { defineConfig, devices } from "@playwright/test"; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - timeout: 2 * 60 * 1000, + timeout: 5 * 60 * 1000, testDir: "./tests", /* Run tests in files in parallel */ fullyParallel: true, /* 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, + retries: process.env.CI ? 2 : 3, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ @@ -32,6 +32,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", testIdAttribute: "data-test-id", + video: "retain-on-failure", }, /* Configure projects for major browsers */ diff --git a/packages/studio-web/tests/studio-web.spec.ts b/packages/studio-web/tests/studio-web.spec.ts index 0bb54840..aa365148 100644 --- a/packages/studio-web/tests/studio-web.spec.ts +++ b/packages/studio-web/tests/studio-web.spec.ts @@ -1,8 +1,12 @@ import { test, expect, Page } from "@playwright/test"; +//import v8toIstanbul from "v8-to-istanbul"; import fs from "fs"; import JSZip from "jszip"; -const assetsPath = "./tests/fixtures/"; +//for vscode +//const assetsPath = "packages/studio-web/tests/fixtures/"; +//for nx +const assetsPath = "tests/fixtures/"; const text = `This is a test. Sentence. @@ -12,47 +16,66 @@ Paragraph. Page.`; const mp3Path = assetsPath + "test-sentence-paragraph-page-56k.mp3"; /** - * Steps to recreate a readlong for tests + * Steps to recreate a readalong for tests */ const makeAReadAlong = async (page: Page) => { - test.step("generate the readalong", async () => { + await test.step("generate the readalong", async () => { await page.getByTestId("ras-text-input").fill(text); await page .getByTestId("audio-btn-group") .getByRole("button", { name: "File" }) .click(); - await page.getByTestId("ras-audio-fileselector").click(); + await page.getByTestId("ras-audio-fileselector").click({ force: true }); await page.getByTestId("ras-audio-fileselector").setInputFiles(mp3Path); //create the readalong - await page.getByTestId("next-step").click(); + await page.getByTestId("next-step").click({ force: true }); + + //wait for edit page to load + await expect(page.getByTestId("ra-header")).toBeVisible(); + await expect(page.getByTestId("ra-header")).toBeEditable(); //edit the headers - await page.getByTestId("ra-header").dblclick(); - await page.getByTestId("ra-header").fill("Sentence Paragraph Page"); + await page + .getByTestId("ra-header") + .fill("Sentence Paragraph Page", { force: true }); + await expect(page.getByTestId("ra-header")).toHaveValue( + "Sentence Paragraph Page", + ); - await page.getByTestId("ra-subheader").dblclick(); - await page.getByTestId("ra-subheader").fill("by me"); + await page + .getByTestId("ra-subheader") + .fill("by me", { force: true, timeout: 200 }); //add translations - await page.locator("#t0b0d0p0s0").getByRole("button").click(); + await page + .locator("#t0b0d0p0s0") + .getByRole("button") + .click({ force: true, timeout: 250 }); - await page.locator("#t0b0d0p0s1").getByRole("button").click(); - await page.locator("#t0b0d0p1s0").getByRole("button").click(); + await page + .locator("#t0b0d0p0s1") + .getByRole("button") + .click({ force: true, timeout: 250 }); + await page + .locator("#t0b0d0p1s0") + .getByRole("button") + .click({ force: true, timeout: 250 }); //update translations - let translation = page.locator("#t0b0d0p0s0translation"); - await translation.click(); - await translation.fill("Ceci est un test."); - translation = page.locator("#t0b0d0p0s1translation"); - await translation.click(); - await translation.fill("Phrase."); + await page + .locator("#t0b0d0p0s0translation") + .fill("Ceci est un test.", { force: true, timeout: 250 }); - translation = page.locator("#t0b0d0p1s0translation"); - await translation.click(); - await translation.fill("Paragraphe."); + await page + .locator("#t0b0d0p0s1translation") + .fill("Phrase.", { force: true, timeout: 250 }); + + await page + .locator("#t0b0d0p1s0translation") + .fill("Paragraphe.", { force: true, timeout: 250 }); //upload a photo to page 1 let fileChooserPromise = page.waitForEvent("filechooser"); @@ -61,7 +84,7 @@ const makeAReadAlong = async (page: Page) => { let fileChooser = await fileChooserPromise; fileChooser.setFiles(assetsPath + "page1.png"); - await page.locator("#t0b0d0p0s0w0").click(); + await page.locator("#t0b0d0p0s0w0").dispatchEvent("click"); //upload a photo to page 2 fileChooserPromise = page.waitForEvent("filechooser"); page.locator("#fileElem--t0b0d1").dispatchEvent("click"); @@ -72,8 +95,20 @@ const makeAReadAlong = async (page: Page) => { test.describe("test studio UI & UX", () => { test.beforeEach(async ({ page }) => { + //await page.coverage.startJSCoverage(); await page.goto("/"); }); + /*test.afterEach(async ({ page }) => { + const coverage = await page.coverage.stopJSCoverage(); + for (const entry of coverage) { + if (entry.source) { + const converter = v8toIstanbul("", 0, { source: entry.source }); + await converter.load(); + converter.applyCoverage(entry.functions); + console.log(JSON.stringify(converter.toIstanbul())); + } + } + });*/ test("should check UI", async ({ page }) => { //tour button is visible await expect(page.getByText("Take the tour!")).toBeVisible(); @@ -102,6 +137,9 @@ test.describe("test studio UI & UX", () => { await page .getByRole("button", { name: "Next (overwrites your data)" }) .click(); + await expect( + page.getByRole("button", { name: "Next (overwrites your data)" }), + ).toHaveCount(0); await page.getByRole("button", { name: "Next" }).click(); await page.getByRole("button", { name: "Next" }).click(); await page.getByRole("button", { name: "Next" }).click(); @@ -170,16 +208,16 @@ test.describe("test studio UI & UX", () => { await page.locator("#t0b0d0p0s1").getByRole("button").click(); await page.locator("#t0b0d0p1s0").getByRole("button").click(); //update translations - let translation = page.locator("#t0b0d0p0s0translation"); + let translation = await page.locator("#t0b0d0p0s0translation"); await translation.click(); await expect(translation).toBeEditable(); await translation.fill("Ceci est un test."); - translation = page.locator("#t0b0d0p0s1translation"); + translation = await page.locator("#t0b0d0p0s1translation"); await translation.click(); await translation.fill("Phrase."); - translation = page.locator("#t0b0d0p1s0translation"); + translation = await page.locator("#t0b0d0p1s0translation"); await translation.click(); await translation.fill("Paragraphe."); await expect(page.locator(".editable__translation")).toHaveCount(3);