Skip to content

Commit

Permalink
RHOAIENG-15152: test(tests/browser): add playwright test for starting…
Browse files Browse the repository at this point in the history
… code-server (#774)

* RHOAIENG-15152: feat(codeserver/e2e): add playwright test for starting code-server

* DO-NOT-COMMIT: trigger GHA

* fixup typo in utils.ts

* fixup default to not using existing CDP browser instance

* fixup remove commented code about using dotenv

* fixup print more meaningful exception when misconfigured

* fixup forgot to change paths in gha

* suppress the npm funding message

* Revert "DO-NOT-COMMIT: trigger GHA"

This reverts commit 629e732.
  • Loading branch information
jiridanek authored Nov 22, 2024
1 parent 3ceb400 commit cd16a28
Show file tree
Hide file tree
Showing 6 changed files with 489 additions and 12 deletions.
49 changes: 47 additions & 2 deletions .github/workflows/build-notebooks-TEMPLATE.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
if: "${{ fromJson(inputs.github).event_name == 'pull_request' }}"
env:
IMAGE_TAG: "${{ github.sha }}"
IMAGE_REGISTRY: "localhost:5000/workbench-images"
IMAGE_REGISTRY: "ghcr.io/${{ github.repository }}/workbench-images"
CONTAINER_BUILD_CACHE_ARGS: "--cache-from ${{ env.CACHE }}"
# We don't have access to image registry, so disable pushing
PUSH_IMAGES: "no"
Expand All @@ -155,7 +155,7 @@ jobs:
TARGET="$FS_SCAN_FOLDER"
TYPE="fs"
else
TARGET="localhost:5000/workbench-images:${{ inputs.target }}-${{ github.sha }}"
TARGET="ghcr.io/${{ github.repository }}/workbench-images:${{ inputs.target }}-${{ github.sha }}"
TYPE="image"
fi
elif [[ "$EVENT_NAME" == "schedule" ]]; then
Expand Down Expand Up @@ -215,5 +215,50 @@ jobs:
cat $REPORT_FOLDER/$REPORT_FILE >> $GITHUB_STEP_SUMMARY
# https://playwright.dev/docs/ci
# https://playwright.dev/docs/docker
# we leave little free disk space after we mount LVM for podman storage
# not enough to install playwright; running playwright in podman uses the space we have
- name: Run Playwright tests
if: ${{ fromJson(inputs.github).event_name == 'pull_request' && contains(inputs.target, 'codeserver') }}
# --ipc=host because Microsoft says so in Playwright docs
# --net=host because testcontainers connects to the Reaper container's exposed port
# we need to pass through the relevant environment variables
# DEBUG configures nodejs debuggers, sets different verbosity as needed
# CI=true is set on every CI nowadays
# PODMAN_SOCK should be mounted to /var/run/docker.sock, other likely mounting locations may not exist (mkdir -p)
# TEST_TARGET is the workbench image the test will run
# --volume(s) let us access docker socket and not clobber host's node_modules
run: |
podman run \
--interactive --rm \
--ipc=host \
--net=host \
--env "CI=true" \
--env "NPM_CONFIG_fund=false" \
--env "DEBUG=testcontainers:*" \
--env "PODMAN_SOCK=/var/run/docker.sock" \
--env "TEST_TARGET" \
--volume ${PODMAN_SOCK}:/var/run/docker.sock \
--volume ${PWD}:/mnt \
--volume /mnt/node_modules \
mcr.microsoft.com/playwright:v1.48.1-noble \
/bin/bash <<EOF
set -Eeuxo pipefail
cd /mnt
npm install -g pnpm && pnpm install
pnpm exec playwright test
exit 0
EOF
working-directory: tests/browser
env:
TEST_TARGET: "ghcr.io/${{ github.repository }}/workbench-images:${{ inputs.target }}-${{ github.sha }}"
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && fromJson(inputs.github).event_name == 'pull_request' && contains(inputs.target, 'codeserver') }}
with:
name: "${{ inputs.target }}_playwright-report"
path: tests/browser/playwright-report/
retention-days: 30

- run: df -h
if: "${{ !cancelled() }}"
12 changes: 2 additions & 10 deletions tests/browser/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { defineConfig, devices } from '@playwright/test';
import * as process from "node:process";

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
Expand Down Expand Up @@ -69,10 +61,10 @@ function getProjects() {
use: { ...devices['Desktop Chrome'], channel: 'chrome',
headless: false, // the CDP browser configured below is not affected by this
/* custom properties, comment out as needed */
connectCDP: 9222, // false | number: connect to an existing browser running at given port
connectCDP: false, // false | number: connect to an existing browser running at given port (e.g. 9222)
codeServerSource: { // prefers url if specified, otherwise will start the specified docker image
// url: "", // not-present | string
image: "quay.io/modh/codeserver:codeserver-ubi9-python-3.11-2024b-20241018", // string
image: "quay.io/modh/codeserver:codeserver-ubi9-python-3.9-20241114-aed66a4", // string
}
},
}
Expand Down
98 changes: 98 additions & 0 deletions tests/browser/tests/codeserver.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as path from "node:path";

import { test as base, expect, chromium } from '@playwright/test';

import {GenericContainer} from "testcontainers";
import {HttpWaitStrategy} from "testcontainers/build/wait-strategies/http-wait-strategy";

import {CodeServer} from "./models/codeserver"

import {setupTestcontainers} from "./testcontainers";

import * as utils from './utils'

// Declare the types of your fixtures.
type MyFixtures = {
connectCDP: false | number;
codeServerSource: {url?: string, image?: string};
codeServer: CodeServer
};
const test = base.extend<MyFixtures>({
connectCDP: [false, {option: true}],
codeServerSource: [{url:'http://localhost:8787'}, {option: true}],
page: async ({ page, connectCDP }, use) => {
if (!connectCDP) {
await use(page)
} else {
// we close the provided page and send onwards our own
await page.close()
{
const browser = await chromium.connectOverCDP(`http://localhost:${connectCDP}`);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
await use(page)
}
}
},
codeServer: [async ({ page, codeServerSource }, use) => {
if (codeServerSource?.url) {
await use(new CodeServer(page, codeServerSource.url))
} else {
const image = codeServerSource.image ?? (() => {
throw new Error("invalid config: codeserver image not specified")
})()
const container = await new GenericContainer(image)
.withExposedPorts(8787)
.withWaitStrategy(new HttpWaitStrategy('/', 8787, {abortOnContainerExit: true}))
.start();
await use(new CodeServer(page, `http://${container.getHost()}:${container.getMappedPort(8787)}`))
await container.stop()
}
}, {timeout: 10 * 60 * 1000}],
});

test.beforeAll(setupTestcontainers)

test('open codeserver', async ({codeServer, page}) => {
await page.goto(codeServer.url)

await codeServer.isEditorVisible()
})

test('wait for welcome screen to load', async ({codeServer, page}, testInfo) => {
await page.goto(codeServer.url);

await codeServer.isEditorVisible()
page.on("console", console.log)

await codeServer.isEditorVisible()
await utils.waitForStableDOM(page, "div.monaco-workbench", 1000, 10000)
await utils.waitForNextRender(page)

await utils.takeScreenshot(page, testInfo, "welcome.png")
})

test('use the terminal to run command', async ({codeServer, page}, testInfo) => {
await page.goto(codeServer.url);

await test.step("Should always see the code-server editor", async () => {
expect(await codeServer.isEditorVisible()).toBe(true)
})

await test.step("should show the Integrated Terminal", async () => {
await codeServer.focusTerminal()
expect(await page.isVisible("#terminal")).toBe(true)
})

await test.step("should execute Terminal command successfully", async () => {
await page.keyboard.type('echo The answer is $(( 6 * 7 )). > answer.txt', {delay: 100})
await page.keyboard.press('Enter', {delay: 100})
})

await test.step("should open the file", async() => {
const file = path.join('/opt/app-root/src', 'answer.txt')
await codeServer.openFile(file)
await expect(page.getByText("The answer is 42.")).toBeVisible()
})

})
Loading

0 comments on commit cd16a28

Please sign in to comment.