diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d73ce13..40fe764d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -212,6 +212,8 @@ jobs: name: Integration tests needs: lite runs-on: ubuntu-latest + permissions: + contents: read env: PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/pw-browsers @@ -219,10 +221,24 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + persist-credentials: false - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Set up Docker Compose + uses: docker/setup-compose-action@v1 + with: + cache-binary: true + + - name: Checkout CKHub sharing service + uses: actions/checkout@v4 + with: + repository: jupytereverywhere/sharing-service + path: ckhub-sharing-service + persist-credentials: false + - name: Download lite app (test mode) uses: actions/download-artifact@v4 with: @@ -252,9 +268,15 @@ jobs: run: jlpm playwright install chromium working-directory: ui-tests - - name: Execute integration tests - working-directory: ui-tests + - name: Start CKHub sharing service and execute integration tests run: | + echo "::group::Starting CKHub sharing service" + cd ckhub-sharing-service + docker compose up --build --detach + cd .. + echo "::endgroup::" + + cd ui-tests jlpm playwright test --browser chromium - name: Upload Playwright Test report diff --git a/ui-tests/tests/sharing-service.spec.ts b/ui-tests/tests/sharing-service.spec.ts new file mode 100644 index 00000000..16bbb6b1 --- /dev/null +++ b/ui-tests/tests/sharing-service.spec.ts @@ -0,0 +1,113 @@ +import { test, expect, Page } from '@playwright/test'; +import type { JupyterLab } from '@jupyterlab/application'; +import type { JSONObject } from '@lumino/coreutils'; + +declare global { + interface Window { + jupyterapp: JupyterLab; + } +} + +async function runCommand(page: Page, command: string, args: JSONObject = {}) { + await page.evaluate( + async ({ command, args }) => { + await window.jupyterapp.commands.execute(command, args); + }, + { command, args } + ); +} + +const TEST_NOTEBOOK = { + cells: [ + { + cell_type: 'code', + execution_count: null, + id: 'test-cell-1', + outputs: [], + metadata: {}, + source: ['print("Hello from CKHub shared notebook!")'] + }, + { + cell_type: 'markdown', + id: 'test-cell-2', + metadata: {}, + source: ['# Test Markdown Cell\n\nThis is a test notebook for sharing.'] + } + ], + metadata: { + kernelspec: { + display_name: 'Python 3 (ipykernel)', + language: 'python', + name: 'python3' + }, + language_info: { + name: 'python', + version: '3.8.0' + } + }, + nbformat: 4, + nbformat_minor: 5 +}; + +async function createTestNotebook(page: Page): Promise { + await page.evaluate(notebookContent => { + const { serviceManager } = window.jupyterapp; + return serviceManager.contents.save('test-notebook.ipynb', { + type: 'notebook', + format: 'json', + content: notebookContent + }); + }, TEST_NOTEBOOK); +} + +async function openTestNotebook(page: Page): Promise { + await runCommand(page, 'docmanager:open', { path: 'test-notebook.ipynb' }); +} + +async function extractShareUrlFromDialog(page: Page): Promise { + const shareUrlElement = await page.waitForSelector('.je-share-link', { timeout: 10000 }); + const shareUrl = await shareUrlElement.textContent(); + + if (!shareUrl) { + throw new Error('Share URL not found in dialog'); + } + + return shareUrl.trim(); +} + +async function getCellContent(page: Page, cellIndex: number = 0): Promise { + return await page.evaluate(index => { + const cells = document.querySelectorAll('.jp-Cell'); + const cell = cells[index]; + if (!cell) return ''; + + const content = cell.querySelector('.cm-content'); + return content?.textContent || ''; + }, cellIndex); +} + +test.beforeEach(async ({ page }) => { + await page.goto('lab/index.html'); + await page.waitForSelector('.jp-LabShell'); +}); + +test.describe('A functional test for the sharing service', () => { + test('Perform round-trip with sharing service', async ({ page, context }) => { + await createTestNotebook(page); + await openTestNotebook(page); + await runCommand(page, 'jupytereverywhere:share-notebook'); + + const shareUrl = await extractShareUrlFromDialog(page); + + const sharedPage = await context.newPage(); + await sharedPage.goto(shareUrl); + await sharedPage.waitForSelector('.jp-LabShell'); + + await runCommand(sharedPage, 'jupytereverywhere:create-copy-notebook'); + + // Wait for view-only header to disappear + await expect(sharedPage.locator('.je-ViewOnlyHeader')).toBeHidden({ timeout: 10000 }); + + await sharedPage.close(); + }); +});