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);