Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: editor e2e #378

Merged
merged 10 commits into from
Jan 9, 2025
33 changes: 17 additions & 16 deletions .github/workflows/end-to-end-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ on:
- push
- workflow_call
jobs:
test-suites:
web-component-suites:
name: Web-Component tests and utilities verification
runs-on: ubuntu-latest
# Stop the occasional rogue instance before the 6h GitHub limit
timeout-minutes: 15
Expand All @@ -22,10 +23,6 @@ jobs:
- name: Install everything
run: npm install

- name: Ng test for studio-web
run: |
npx nx build web-component
npx nx test:once studio-web
- name: Cypress run for web-component
uses: cypress-io/github-action@v6
with:
Expand Down Expand Up @@ -54,15 +51,15 @@ jobs:
npx nx bundle web-component
git status
git diff --word-diff=porcelain --word-diff-regex=... --color | perl -ple 's/^(\x1b[^ -+]{0,6})? (.{81,})$/$1 . " " . substr($2, 0, 40) . " [... " . (length($2)-80) . " bytes ...] " . substr($2, -40)/ex'
playwright-tests:
name: Run Playwright test-suites
studio-e2e-tests:
name: Studio Web test-suites
timeout-minutes: 60
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
shardIndex: [1, 2, 3]
shardTotal: [3]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -72,22 +69,26 @@ jobs:
run: |
git clone https://github.com/ReadAlongs/Studio
cd Studio
pip install -e . -r requirements.api.txt
pip install -e .[api]
./run-web-api.sh &
# wait for the API to be up
curl --retry 20 --retry-delay 1 --retry-all-errors http://localhost:8000/api/v1/langs
- name: Install everything
run: npm install
- name: Install dependencies
run: npm ci
- name: Ng test for studio-web
run: |
npx nx build web-component
npx nx test:once studio-web
- name: Run studio-web in the background
run: |
npx nx build web-component
npx nx run-many --targets=serve,serve-fr,serve-es --projects=web-component,studio-web --parallel 6 &

# wait for the studio web to be up
sleep 100
curl --retry 20 --retry-delay 30 --retry-all-errors http://localhost:4200
sleep 50
curl --retry 20 --retry-delay 10 --retry-all-errors http://localhost:4200 > /dev/null
- name: Run Playwright tests for studio-web
run: |
npx playwright install --with-deps chromium
Expand All @@ -102,8 +103,8 @@ jobs:
merge-reports:
# Merge reports after playwright-tests, even if some shards have failed
if: ${{ !cancelled() }}
needs: [playwright-tests]
name: "Merge playwright reports"
needs: [studio-e2e-tests]
name: "Merge playwright reports from studio-web end-to-end tests"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -123,10 +124,10 @@ jobs:
pattern: blob-report-*
merge-multiple: true

- name: Merge into HTML Report
- name: Merge into a single HTML Report
run: npx playwright merge-reports --reporter=html,github ./all-blob-reports

- name: Upload HTML report
- name: Upload single HTML report
uses: actions/upload-artifact@v4
with:
name: html-report--attempt-${{ github.run_attempt }}
Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ This mono repo, formerly called Web-Component, now called Studio-Web, combines f
- [Studio-Web](#studio-web)
- [Understanding where the components come from when you run locally](#understanding-where-the-components-come-from-when-you-run-locally)
- [Testing](#testing)
- [TL;DR](#tldr)
- [Web-Component](#web-component-1)
- [Studio-Web](#studio-web-1)
- [End-to-End tests](#end-to-end-tests)
- [Internationalization (i18n) and localization (l10n)](#internationalization-i18n-and-localization-l10n)
- [Build \& Publish](#build--publish-the-web-component)
- [Build \& Publish the Web Component](#build--publish-the-web-component)
- [Preparing to publish the Web Component \& Angular Wrapper](#preparing-to-publish-the-web-component--angular-wrapper)
- [Web Component \& Angular Wrapper - via a tag push](#web-component--angular-wrapper---via-a-tag-push)
- [Web Component \& Angular Wrapper - manually - please don't do this!](#web-component--angular-wrapper---manually---please-dont-do-this)
- [Build \& Deploy the Studio-Web App](#build--deploy-the-studio-web-app)
- [Build \& Deploy the Studio-Web app](#build--deploy-the-studio-web-app)
- [Automated Deployment](#automated-deployment)
- [Build Studio-Web and deploy it somewhere else](#build-studio-web-and-deploy-it-somewhere-else)
- [Maintainers](#maintainers)
- [Contributing](#contributing)
- [Acknowledgements](#acknowledgements)
Expand Down Expand Up @@ -189,6 +193,10 @@ to serve or import. In the instructions above, we actually show two methods you
npm install
npx nx run-many --targets=serve-test-data,serve,test:once --projects=web-component
# Ctrl-C once "✔ All specs passed! 01:01 34 34" appears (34 specs as of writing)
npx playwright install --with-deps firefox chromium webkit
npx nx run-many --targets=serve-test-data,serve-web-api,serve,serve-fr,serve-es --projects=web-component,studio-web --parallel 6
npx nx e2e studio-web
#Expect "47 passed (4.6m)" (47 tests as of writing)
npx nx test:once studio-web
# Expect "TOTAL: 25 SUCCESS" (25 tests as of writing)
npx nx extract-i18n studio-web
Expand Down Expand Up @@ -224,10 +232,29 @@ Alternatively run together as:
#### Studio-Web

To run the unit tests for Studio-Web, first build `web-component` in one of the ways listed
above (or just `npx nx build web-component`) if you have not already done so, and then run:
above (or just `npx nx build web-component`) if you have not already done so, and then
run:

npx nx test:once studio-web

##### End-to-End tests

To run the end-to-end tests for Studio-Web, please check the following:

- Ensure that you have built the `web-component` (run `npx nx build web-component`)
- Check that `studio-cli` is installed and running (run `npx nx serve-web-api studio-web`)
- Confirm that `studio-web` is up and running (run `npx nx run-many --targets=serve,serve-fr,serve-es --projects=studio-web --parallel 3`). Your browser must be able to load `http://localhost:4200/` before you proceed.
- Verify that `playwright` is installed and configured (run `npx playwright install --with-deps firefox chromium webkit`)

Alternatively run together as:

npx playwright install --with-deps firefox chromium webkit && npx nx run-many --targets=serve-test-data,serve-web-api,serve,serve-fr,serve-es --projects=web-component,studio-web --parallel 6

Once you have confirmed that everything is online and working run:
`npx nx e2e studio-web` and wait for the report.

**PS**: If you want to run a spec interactively you can run `npx nx e2e-ui studio-web` to get playwright interactive user interface.

### Internationalization (i18n) and localization (l10n)

`studio-web` is localized in French and Spanish. When you add new strings that need localizing,
Expand Down
2 changes: 1 addition & 1 deletion packages/studio-web/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 2 : 3,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : 2,
workers: process.env.CI ? 4 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? [["blob", { open: "never" }]] : "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
Expand Down
7 changes: 6 additions & 1 deletion packages/studio-web/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
ReadAlong Studio
</span>
<span class="nav-spacer"></span>
<button class="d-md-none" mat-button [matMenuTriggerFor]="menu">
<button
class="d-md-none"
data-test-id="menu-toggle"
mat-button
[matMenuTriggerFor]="menu"
>
<mat-icon>menu</mat-icon>
</button>
<mat-menu #menu="matMenu">
Expand Down
12 changes: 8 additions & 4 deletions packages/studio-web/src/app/shared/download/download.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,14 @@ Please host all assets on your server, include the font and package imports defi
}

registerDownloadEvent(selectedOutputFormat: SupportedOutputs, from: string) {
const win = window;
(win as any).plausible(`Download`, {
props: { fileType: selectedOutputFormat, downloadSource: from },
});
try {
const win = window;
(win as any).plausible(`Download`, {
props: { fileType: selectedOutputFormat, downloadSource: from },
});
} catch (err) {
console.error(err);
}
}

async createSingleFileBlob(
Expand Down
95 changes: 95 additions & 0 deletions packages/studio-web/tests/editor/check-page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { test, expect } from "@playwright/test";
import { testAssetsPath, disablePlausible } from "../test-commands";
test.describe.configure({ mode: "parallel" });
test("should check editor UI", async ({ page, isMobile }) => {
await page.goto("/", { waitUntil: "load" });

await disablePlausible(page);
if (isMobile) {
await page.getByTestId("menu-toggle").click();
}
await page.getByRole("button", { name: /Editor/ }).click();
await expect(
page.getByRole("button", { name: "Take the tour!" }),
"Tour button is visible",
).toBeVisible();
await expect(
page.locator("#updateRAS"),
"Choose file is visible",
).toBeVisible();
let fileChooserPromise = page.waitForEvent("filechooser");
await page.locator("#updateRAS").click();
let fileChooser = await fileChooserPromise;
fileChooser.setFiles(testAssetsPath + "sentence-paragr.html");
//check audio bar
/**
* We are using css classes instead of test-id because this is an external plugin
* failure here means the plugin version has changed we should test impact on our app
*/

await expect(
page.locator("#audioToolbar"),
"audio bar should exist",
).toHaveCount(1);
await expect(
page.locator("segment.wavesurfer-segment"),
"should seven audio segments",
).toHaveCount(7);
await expect(
page.locator("segment.wavesurfer-segment:first-of-type > .segment-content"),
"audio segments text should be editable",
).toHaveAttribute("contenteditable", "true");
await expect(
page.locator(
"segment.wavesurfer-segment:first-of-type > .wavesurfer-handle",
),
"audio segments boundaries should exist",
).toHaveCount(2);

//check readalong
await expect(
page.locator("#readalongContainer"),
"should check that readalong is loading",
).not.toBeEmpty();
const header = page.locator(
"#readalongContainer span[slot=read-along-header]",
);
await expect(header, "should have correct title").toContainText(
"Sentence Paragraph Page",
);
await expect(header, "should have editable title").toHaveAttribute(
"contenteditable",
"true",
);
const subheader = page.locator(
"#readalongContainer span[slot=read-along-subheader]",
);
await expect(subheader, "should have correct subtitle").toContainText(
"by me",
);
await expect(subheader, "should have editable subtitle").toHaveAttribute(
"contenteditable",
"true",
);
await page.locator("#t0b0d0p1s0").scrollIntoViewIfNeeded();
await expect(
page.locator("#t0b0d0p0s0").getByLabel("Remove translation"),
"should have translation for first paragraph first sentence",
).toBeVisible();
await expect(
page.locator("#t0b0d0p0s1").getByRole("button", { name: "add" }),
"should not have translation for first paragraph second sentence",
).toBeVisible();
await expect(
page.locator("#t0b0d0p1s0").getByLabel("Remove translation"),
"should have translation for second paragraph ",
).toBeVisible();
await expect(
page.locator("#fileElem--t0b0d0"),
"should not have image on first page",
).toHaveCount(1);
await expect(
page.locator("#t0b0d1 img"),
"should have image on second page",
).toHaveCount(1);
});
40 changes: 40 additions & 0 deletions packages/studio-web/tests/editor/edit-images.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { test, expect } from "@playwright/test";
import { testAssetsPath, editorDefaultBeforeEach } from "../test-commands";
test.describe.configure({ mode: "parallel" });

test("should edit images (editor)", async ({ page, isMobile }) => {
await expect(async () => {
await editorDefaultBeforeEach(page, isMobile);
}).toPass();
await page.locator("#updateRAS").waitFor({ state: "visible" });
await expect(
page.locator("#fileElem--t0b0d0"),
"should not have image on first page",
).toHaveCount(1);
//upload a photo to page 1
let fileChooserPromise = page.waitForEvent("filechooser");
page.locator("#fileElem--t0b0d0").dispatchEvent("click");

let fileChooser = await fileChooserPromise;
fileChooser.setFiles(testAssetsPath + "page1.png");
await expect(
page.locator("#t0b0d0 img"),
"should have image on first page",
).toHaveCount(1);
//delete photo page 2 and re-add
const progressBar = page.getByTestId("progress-bar");
const progressBarBoundingBox = await progressBar.boundingBox();

await progressBar.click({
force: true,
position: {
x: progressBarBoundingBox?.x || 0,
y: progressBarBoundingBox ? progressBarBoundingBox.width * 0.9 : 0,
},
});
await page.locator("#t0b0d1").getByTestId("delete-button").click();
await expect(
page.locator("#fileElem--t0b0d1"),
"should remove image on second page",
).toHaveCount(1);
});
45 changes: 45 additions & 0 deletions packages/studio-web/tests/editor/edit-translations.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { test, expect } from "@playwright/test";
import { editorDefaultBeforeEach } from "../test-commands";
test.describe.configure({ mode: "parallel" });

test("should edit translations (editor)", async ({ page, isMobile }) => {
await expect(async () => {
await editorDefaultBeforeEach(page, isMobile);
}).toPass();
await page.locator("#t0b0d0p0s0translation").waitFor({ state: "visible" });
//edit first sentence translation
await page.locator("#t0b0d0p0s0translation").fill("Un vrai test.");
await expect(page.locator("#t0b0d0p0s0translation")).toContainText(
"Un vrai test.",
);
//add translation to second sentence
await page
.locator("#t0b0d0p0s1")
.getByTestId("add-translation-button")
.click();
await page.locator("#t0b0d0p0s1translation").fill("Phrase.");
//remove third sentence
await page
.locator("#t0b0d0p1s0")
.getByTestId("remove-translation-button")
.click();
await page
.locator("#t0b0d0p1s0")
.getByTestId("add-translation-button")
.waitFor({ state: "visible" });
//toggle translations
await page.getByTestId("translation-toggle").click();
await expect(
page.locator(".sentence__translation.invisible"),
" translations should be hidden",
).not.toHaveCount(0);
await page.getByTestId("translation-toggle").click();
await expect(
page.locator(".sentence__translation.invisible"),
" translations should not be hidden",
).toHaveCount(0);
await expect(
page.getByTestId("translation-line"),
" translations should not be hidden",
).toHaveCount(2);
});
Loading
Loading