From c92329b76b5b7b329fda58590fea835a5f4301d5 Mon Sep 17 00:00:00 2001 From: Jake Wagoner Date: Mon, 5 Feb 2024 16:27:35 -0700 Subject: [PATCH] Add playwright --- .github/workflows/playwright.yml | 61 ++++ .gitignore | 6 + e2e-tests/alttext.spec.ts | 124 +++++++ package.json | 2 +- playwright.config.ts | 44 +++ .../mock-data/simpsons/simpsons_alttxt.json | 7 + .../simpsons/simpsons_annotations.json | 10 + .../mock-data/simpsons/simpsons_data.json | 319 ++++++++++++++++++ yarn.lock | 26 ++ 9 files changed, 598 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 e2e-tests/alttext.spec.ts create mode 100644 playwright.config.ts create mode 100644 playwright/mock-data/simpsons/simpsons_alttxt.json create mode 100644 playwright/mock-data/simpsons/simpsons_annotations.json create mode 100644 playwright/mock-data/simpsons/simpsons_data.json diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000..181611f5 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,61 @@ +name: Playwright Tests + +on: + pull_request: + branches: + - main + +jobs: + test: + name: 🧪 ${{ matrix.project }} E2E Tests + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - project: chromium + os: ubuntu-latest + cache_dir: ~/.cache/ms-playwright + + - project: firefox + os: ubuntu-latest + cache_dir: ~/.cache/ms-playwright + + - project: webkit + os: macos-latest + cache_dir: ~/Library/Caches/ms-playwright + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: lts/* + cache: "yarn" + + - name: Install dependencies + run: yarn install --immutable + + - name: Write out playwright version + run: yarn --silent playwright --version > .playwright-version + + - name: ⚡️ Cache playwright binaries + uses: actions/cache@v3 + id: playwright-cache + with: + path: ${{ matrix.cache_dir }} + key: ${{ runner.os }}-${{ matrix.project }}-pw-${{ hashFiles('**/.playwright-version') }} + + - name: Install Playwright Browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: yarn playwright install --with-deps ${{ matrix.project }} + + - name: Run Playwright tests + run: yarn test --project=${{ matrix.project }} + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: playwright-report + path: playwright-report \ No newline at end of file diff --git a/.gitignore b/.gitignore index b140d13c..e5e9d9fd 100644 --- a/.gitignore +++ b/.gitignore @@ -151,4 +151,10 @@ psd thumb sketch +# playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ + # End of https://www.toptal.com/developers/gitignore/api/react,node,joed diff --git a/e2e-tests/alttext.spec.ts b/e2e-tests/alttext.spec.ts new file mode 100644 index 00000000..0369c24d --- /dev/null +++ b/e2e-tests/alttext.spec.ts @@ -0,0 +1,124 @@ +/* eslint-disable testing-library/prefer-screen-queries */ +import { test, expect } from '@playwright/test'; +import mockData from '../playwright/mock-data/simpsons/simpsons_data.json'; +import mockAnnotations from '../playwright/mock-data/simpsons/simpsons_annotations.json'; +import mockAltText from '../playwright/mock-data/simpsons/simpsons_alttxt.json'; + +test.beforeEach(async ({ page }) => { + await page.route('*/**/api/**', async (route) => { + const url = route.request().url(); + let json; + + if (url) { + if (url.includes('workspaces/Upset%20Examples/tables/simpsons/rows/?limit=9007199254740991')) { + json = mockData; + await route.fulfill({ json }); + } else if (url.includes('workspaces/Upset%20Examples/tables/simpsons/annotations/')) { + json = mockAnnotations; + await route.fulfill({ json }); + } else if (url.includes('alttxt')) { + json = mockAltText; + await route.fulfill({ json }); + } else if (url.includes('workspaces/Upset%20Examples/sessions/table/193/state/')) { + await route.fulfill({ status: 200 }); + } else { + await route.continue(); + } + } else { + await route.abort(); + } + }); +}); + +test('Alt Text', async ({ page }) => { + await page.goto('http://localhost:3000/?workspace=Upset+Examples&table=simpsons&sessionId=193'); + + const altTextSidebarButton = page.getByLabel('Open alt text sidebar'); + await altTextSidebarButton.click(); + + const altTextSidebar = page.getByLabel('Alt Text Sidebar', { exact: true }); + await expect(altTextSidebar).toBeVisible(); + + const altTextHeading = page.getByRole('heading', { name: 'Alt Text' }); + await expect(altTextHeading).toBeVisible(); + + /// ///////////////// + // Plot Information + /// ///////////////// + const plotInformation = page.getByRole('button', { name: 'Plot Information' }); + await expect(plotInformation).toBeVisible(); + await plotInformation.click(); + + const editPlotInformationButton = page.getByLabel('Toggle editable descriptions'); + await expect(editPlotInformationButton).toBeVisible(); + await editPlotInformationButton.click(); + + const datasetDescriptionInput = page.getByPlaceholder('eg: movie genres and ratings'); + await expect(datasetDescriptionInput).toBeVisible(); + await expect(datasetDescriptionInput).toBeEditable(); + await datasetDescriptionInput.click(); + await datasetDescriptionInput.fill('Test dataset description'); + + const setsInput = page.getByPlaceholder('eg: movie genres (dataset'); + await expect(setsInput).toBeVisible(); + await expect(setsInput).toBeEditable(); + await setsInput.click(); + await setsInput.fill('Test sets value'); + + const itemsInput = page.getByPlaceholder('eg: movies (dataset rows)'); + await expect(itemsInput).toBeVisible(); + await expect(itemsInput).toBeEditable(); + await itemsInput.click(); + await itemsInput.fill('Test items value'); + + const plotInformationOutput = page.getByText('This UpSet plot shows test'); + await expect(plotInformationOutput).toBeVisible(); + await expect(plotInformationOutput).toHaveText('This UpSet plot shows Test dataset description. The sets are Test sets value. The items are Test items value.'); + + await page.getByRole('button', { name: 'Save' }).click(); + await plotInformation.click(); + + /// ///////////////// + // Alt Text Output + /// ///////////////// + const UpSetIntroduction = { + heading: page.getByRole('heading', { name: 'UpSet Introduction' }), + content: page.getByText('This is an UpSet plot that'), + }; + await expect(UpSetIntroduction.heading).toBeVisible(); + await expect(UpSetIntroduction.content).toBeVisible(); + await expect(UpSetIntroduction.content).toHaveText('This is an UpSet plot that visualizes set intersection. To learn about UpSet plots, visit https://upset.app.'); + + const datasetProperties = { + heading: page.getByRole('heading', { name: 'Dataset Properties' }), + content: page.getByText('The dataset contains 6 sets'), + }; + + await expect(datasetProperties.heading).toBeVisible(); + await expect(datasetProperties.content).toBeVisible(); + await expect(datasetProperties.content).toHaveText('The dataset contains 6 sets, and 44 elements, of which 6 are shown in the plot.'); + + const setProperties = { + heading: page.getByRole('heading', { name: 'Set Properties', exact: true }), + content: page.getByText('The largest set is Male with'), + }; + await expect(setProperties.heading).toBeVisible(); + await expect(setProperties.content).toBeVisible(); + await expect(setProperties.content).toHaveText('The largest set is Male with 18 elements, followed by School with 6, Duff Fan with 6, Evil with 6, Power Plant with 5, and Blue Hair with 3.'); + + const intersectionProperties = { + heading: page.getByRole('heading', { name: 'Intersection Properties' }), + content: page.getByText('The plot is sorted by size.'), + }; + await expect(intersectionProperties.heading).toBeVisible(); + await expect(intersectionProperties.content).toBeVisible(); + await expect(intersectionProperties.content).toHaveText('The plot is sorted by size. There are 12 non-empty intersections, all of which are shown in the plot. The largest 5 intersections are School Male (3), the empty intersection (3), Just Male (3), Duff_Fan Male Power_Plant (3), and Evil Male (2).'); + + const statisticalInformation = { + heading: page.getByRole('heading', { name: 'Statistical Information' }), + content: page.getByText('The average intersection size'), + }; + await expect(statisticalInformation.heading).toBeVisible(); + await expect(statisticalInformation.content).toBeVisible(); + await expect(statisticalInformation.content).toHaveText('The average intersection size is 2, and the median is 2. The 90th percentile is 3, and the 10th percentile is 1. The largest set, Male, is present in 75.0% of all non-empty intersections. The smallest set, Blue Hair, is present in 0.0% of all non-empty intersections.'); +}); diff --git a/package.json b/package.json index ac945ee7..264f4d4c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "dev": "turbo run dev --parallel --no-cache", "storybook": "turbo run storybook", - "test": "turbo run test", + "test": "playwright test", "build": "turbo run build --force", "prepublish": "lerna run prepublish", "publish-canary": "echo 'Do something'", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..d50291be --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,44 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e-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, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + stdout: 'ignore', + stderr: 'pipe', + }, +}); diff --git a/playwright/mock-data/simpsons/simpsons_alttxt.json b/playwright/mock-data/simpsons/simpsons_alttxt.json new file mode 100644 index 00000000..295a862e --- /dev/null +++ b/playwright/mock-data/simpsons/simpsons_alttxt.json @@ -0,0 +1,7 @@ +{ + "alttxt": { + "techniqueDescription": "This is an UpSet plot that visualizes set intersection. To learn about UpSet plots, visit https://upset.app.", + "shortDescription": "This is an UpSet plot which shows set intersection of 6 sets out of 6 sets and the largest intersection is School Male (3). The plot is sorted by size and 12 non-empty intersections are shown.", + "longDescription": "# UpSet Introduction\nThis is an UpSet plot that visualizes set intersection. To learn about UpSet plots, visit https://upset.app.\n\n# Dataset Properties\nThe dataset contains 6 sets, and 44 elements, of which 6 are shown in the plot.\n\n# Set Properties\nThe largest set is Male with 18 elements, followed by School with 6, Duff Fan with 6, Evil with 6, Power Plant with 5, and Blue Hair with 3.\n\n# Intersection Properties\nThe plot is sorted by size. There are 12 non-empty intersections, all of which are shown in the plot. The largest 5 intersections are School Male (3), the empty intersection (3), Just Male (3), Duff_Fan Male Power_Plant (3), and Evil Male (2).\n\n# Statistical Information\nThe average intersection size is 2, and the median is 2. The 90th percentile is 3, and the 10th percentile is 1. The largest set, Male, is present in 75.0% of all non-empty intersections. The smallest set, Blue Hair, is present in 0.0% of all non-empty intersections.\n\n" + } +} diff --git a/playwright/mock-data/simpsons/simpsons_annotations.json b/playwright/mock-data/simpsons/simpsons_annotations.json new file mode 100644 index 00000000..9db2ce15 --- /dev/null +++ b/playwright/mock-data/simpsons/simpsons_annotations.json @@ -0,0 +1,10 @@ +{ + "Name": "label", + "School": "boolean", + "Blue Hair": "boolean", + "Duff Fan": "boolean", + "Evil": "boolean", + "Male": "boolean", + "Power Plant": "boolean", + "Age": "number" +} diff --git a/playwright/mock-data/simpsons/simpsons_data.json b/playwright/mock-data/simpsons/simpsons_data.json new file mode 100644 index 00000000..cf883dc7 --- /dev/null +++ b/playwright/mock-data/simpsons/simpsons_data.json @@ -0,0 +1,319 @@ +{ + "count": 24, + "next": null, + "previous": null, + "results": [ + { + "_key": "40726825", + "_id": "simpsons/40726825", + "_rev": "_fQwAX56---", + "Name": "Lisa", + "School": true, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": false, + "Power Plant": false, + "Age": 8 + }, + { + "_key": "40726826", + "_id": "simpsons/40726826", + "_rev": "_fQwAX56--_", + "Name": "Bart", + "School": true, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 10 + }, + { + "_key": "40726827", + "_id": "simpsons/40726827", + "_rev": "_fQwAX56--A", + "Name": "Homer", + "School": false, + "Blue Hair": false, + "Duff Fan": true, + "Evil": false, + "Male": true, + "Power Plant": true, + "Age": 40 + }, + { + "_key": "40726828", + "_id": "simpsons/40726828", + "_rev": "_fQwAX56--B", + "Name": "Marge", + "School": false, + "Blue Hair": true, + "Duff Fan": false, + "Evil": false, + "Male": false, + "Power Plant": false, + "Age": 36 + }, + { + "_key": "40726829", + "_id": "simpsons/40726829", + "_rev": "_fQwAX56--C", + "Name": "Maggie", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": false, + "Power Plant": false, + "Age": 1 + }, + { + "_key": "40726830", + "_id": "simpsons/40726830", + "_rev": "_fQwAX56--D", + "Name": "Barney", + "School": false, + "Blue Hair": false, + "Duff Fan": true, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 39 + }, + { + "_key": "40726831", + "_id": "simpsons/40726831", + "_rev": "_fQwAX56--E", + "Name": "Mr. Burns", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": true, + "Male": true, + "Power Plant": true, + "Age": 90 + }, + { + "_key": "40726832", + "_id": "simpsons/40726832", + "_rev": "_fQwAX56--F", + "Name": "Mo", + "School": false, + "Blue Hair": false, + "Duff Fan": true, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 41 + }, + { + "_key": "40726833", + "_id": "simpsons/40726833", + "_rev": "_fQwAX56--G", + "Name": "Ned", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 42 + }, + { + "_key": "40726834", + "_id": "simpsons/40726834", + "_rev": "_fQwAX56--H", + "Name": "Milhouse", + "School": true, + "Blue Hair": true, + "Duff Fan": false, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 10 + }, + { + "_key": "40726835", + "_id": "simpsons/40726835", + "_rev": "_fQwAX56--I", + "Name": "Grampa", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 85 + }, + { + "_key": "40726836", + "_id": "simpsons/40726836", + "_rev": "_fQwAX56--J", + "Name": "Krusty", + "School": false, + "Blue Hair": false, + "Duff Fan": true, + "Evil": true, + "Male": true, + "Power Plant": false, + "Age": 46 + }, + { + "_key": "40726837", + "_id": "simpsons/40726837", + "_rev": "_fQwAX56--K", + "Name": "Smithers", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": true, + "Male": true, + "Power Plant": true, + "Age": 33 + }, + { + "_key": "40726838", + "_id": "simpsons/40726838", + "_rev": "_fQwAX56--L", + "Name": "Ralph", + "School": true, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 8 + }, + { + "_key": "40726839", + "_id": "simpsons/40726839", + "_rev": "_fQwAX56--M", + "Name": "Sideshow Bob", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": true, + "Male": true, + "Power Plant": false, + "Age": 37 + }, + { + "_key": "40726840", + "_id": "simpsons/40726840", + "_rev": "_fQwAX56--N", + "Name": "Kent Brockman", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 45 + }, + { + "_key": "40726841", + "_id": "simpsons/40726841", + "_rev": "_fQwAX56--O", + "Name": "Fat Tony", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": true, + "Male": true, + "Power Plant": false, + "Age": 50 + }, + { + "_key": "40726842", + "_id": "simpsons/40726842", + "_rev": "_fQwAX56--P", + "Name": "Jacqueline Bouvier ", + "School": false, + "Blue Hair": true, + "Duff Fan": false, + "Evil": false, + "Male": false, + "Power Plant": false, + "Age": 76 + }, + { + "_key": "40726843", + "_id": "simpsons/40726843", + "_rev": "_fQwAX56--Q", + "Name": "Patty Bouvier", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": false, + "Power Plant": false, + "Age": 45 + }, + { + "_key": "40726844", + "_id": "simpsons/40726844", + "_rev": "_fQwAX56--R", + "Name": "Selma Bouvier", + "School": false, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": false, + "Power Plant": false, + "Age": 45 + }, + { + "_key": "40726845", + "_id": "simpsons/40726845", + "_rev": "_fQwAX56--S", + "Name": "Lenny Leonard", + "School": false, + "Blue Hair": false, + "Duff Fan": true, + "Evil": false, + "Male": true, + "Power Plant": true, + "Age": 38 + }, + { + "_key": "40726846", + "_id": "simpsons/40726846", + "_rev": "_fQwAX56--T", + "Name": "Carl Carlson", + "School": false, + "Blue Hair": false, + "Duff Fan": true, + "Evil": false, + "Male": true, + "Power Plant": true, + "Age": 37 + }, + { + "_key": "40726847", + "_id": "simpsons/40726847", + "_rev": "_fQwAX56--U", + "Name": "Nelson", + "School": true, + "Blue Hair": false, + "Duff Fan": false, + "Evil": true, + "Male": true, + "Power Plant": false, + "Age": 11 + }, + { + "_key": "40726848", + "_id": "simpsons/40726848", + "_rev": "_fQwAX56--V", + "Name": "Martin Prince", + "School": true, + "Blue Hair": false, + "Duff Fan": false, + "Evil": false, + "Male": true, + "Power Plant": false, + "Age": 10 + } + ] +} diff --git a/yarn.lock b/yarn.lock index c5c6d3b3..b68e46e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3204,6 +3204,13 @@ form-data "^4.0.0" opener "^1.5.2" +"@playwright/test@^1.41.2": + version "1.41.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.41.2.tgz#bd9db40177f8fd442e16e14e0389d23751cdfc54" + integrity sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg== + dependencies: + playwright "1.41.2" + "@pmmmwh/react-refresh-webpack-plugin@^0.5.3": version "0.5.11" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz#7c2268cedaa0644d677e8c4f377bc8fb304f714a" @@ -10504,6 +10511,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fsevents@^1.2.7: version "1.2.13" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" @@ -15285,6 +15297,20 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" +playwright-core@1.41.2: + version "1.41.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.41.2.tgz#db22372c708926c697acc261f0ef8406606802d9" + integrity sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA== + +playwright@1.41.2: + version "1.41.2" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.41.2.tgz#4e760b1c79f33d9129a8c65cc27953be6dd35042" + integrity sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A== + dependencies: + playwright-core "1.41.2" + optionalDependencies: + fsevents "2.3.2" + pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"