diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d38a27ccec..ea0cbd3f21 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,12 +6,14 @@ on: - main jobs: - pr: - runs-on: ${{ matrix.os }} + reproduce-markdown-preview-side-by-side: + name: markdown-preview-side-by-side (attempt ${{ matrix.attempt }}) + runs-on: ubuntu-24.04 strategy: fail-fast: false + max-parallel: 1 matrix: - os: [ubuntu-24.04, macos-26, windows-2022] + attempt: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] timeout-minutes: 240 steps: - uses: actions/checkout@v7 @@ -34,66 +36,17 @@ jobs: **/.vscode-ffmpeg **/.vscode-resolve-source-map-cache key: ${{ runner.os }}-cacheNodeModules-${{ steps.nodeModulesCacheKey.outputs.value }} - - name: Compute source maps cache key - id: sourceMapsCacheKey - run: echo "value=$(node packages/build/src/computeSourceMapsCacheKey.ts)" >> $GITHUB_OUTPUT - shell: bash - - uses: actions/cache@v6 - id: source-maps-cache - with: - path: | - **/.vscode-source-maps - **/.vscode-sources - key: ${{ runner.os }}-cacheSourceMaps-${{ steps.sourceMapsCacheKey.outputs.value }} - name: npm ci run: npm ci --ignore-scripts && npm run postinstall if: steps.npm-cache.outputs.cache-hit != 'true' - - run: npm run type-check - - run: npm run lint - run: npm run build - - run: npx lerna run test --since origin/main - - name: Run headless test (with videos) - if: matrix.os != 'macos-26' - uses: coactions/setup-xvfb@v1 - with: - run: node packages/cli/bin/test.js --cwd packages/e2e --record-video - - name: Run headless test - uses: coactions/setup-xvfb@v1 - with: - run: npm run e2e - - name: Run headless test (flakyness check) - if: matrix.os != 'macos-26' - uses: coactions/setup-xvfb@v1 - with: - run: npm run e2e:check-flakyness - - name: Run headless test (second flakyness check) - if: matrix.os != 'macos-26' - uses: coactions/setup-xvfb@v1 - with: - run: npm run e2e:check-flakyness - - name: Run headless test (check leaks, event listener count) - if: matrix.os != 'macos-26' - uses: coactions/setup-xvfb@v1 - with: - run: node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after - - name: Run headless test (check leaks, event listeners) - if: matrix.os == 'ubuntu-24.04' - uses: coactions/setup-xvfb@v1 - with: - run: node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure event-listeners - - name: Run Memory City smoke test - if: matrix.os == 'ubuntu-24.04' + - name: Run markdown-preview-side-by-side uses: coactions/setup-xvfb@v1 with: - run: node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure memory-city --only ^editor-open.ts --workers 1 - - name: Generate charts - run: node packages/charts/src/main.ts - - name: Validate Memory City artifact - if: matrix.os == 'ubuntu-24.04' - run: node packages/visualizations/src/validate.ts + run: node packages/cli/bin/test.js --cwd packages/e2e --record-video --only ^markdown-preview-side-by-side.ts --workers 1 --run-skipped-tests-anyway - uses: actions/upload-artifact@v7 if: always() with: - name: vscode-videos-${{ runner.os }} + name: markdown-preview-side-by-side-attempt-${{ matrix.attempt }} path: ./.vscode-videos include-hidden-files: true diff --git a/packages/e2e/src/markdown-preview-side-by-side.ts b/packages/e2e/src/markdown-preview-side-by-side.ts index b313e4e87e..4ce74cb0df 100644 --- a/packages/e2e/src/markdown-preview-side-by-side.ts +++ b/packages/e2e/src/markdown-preview-side-by-side.ts @@ -31,7 +31,7 @@ export const run = async ({ Editor, MarkdownPreview, QuickPick, WellKnownCommand await Editor.focusBottomEditorGroup() await Editor.open('second.md') await QuickPick.executeCommand(WellKnownCommands.MarkdownOpenPreviewToTheSide) - const subFrame2 = await MarkdownPreview.shouldBeVisible() + const subFrame2 = await MarkdownPreview.shouldBeVisible(1) await MarkdownPreview.shouldHaveHeading(subFrame2, 'second-document') await Editor.closeAll() diff --git a/packages/e2e/types/pageobject-api.d.ts b/packages/e2e/types/pageobject-api.d.ts index 09968b5698..cf5e361cc1 100644 --- a/packages/e2e/types/pageobject-api.d.ts +++ b/packages/e2e/types/pageobject-api.d.ts @@ -510,7 +510,7 @@ export interface LanguageModelEditor { open(): Promise } export interface MarkdownPreview { - shouldBeVisible(): Promise + shouldBeVisible(iframeIndex?: any): Promise shouldHaveCodeBlocks(subFrame: any, count: any): Promise shouldHaveCodeBlockWithLanguage(subFrame: any, language: any): Promise shouldHaveHeading(subFrame: any, id: any): Promise diff --git a/packages/page-object/src/parts/MarkdownPreview/MarkdownPreview.ts b/packages/page-object/src/parts/MarkdownPreview/MarkdownPreview.ts index 846bce7bb1..98c7f7e987 100644 --- a/packages/page-object/src/parts/MarkdownPreview/MarkdownPreview.ts +++ b/packages/page-object/src/parts/MarkdownPreview/MarkdownPreview.ts @@ -2,7 +2,7 @@ import type { CreateParams } from '../CreateParams/CreateParams.ts' export const create = ({ expect, page, VError }: CreateParams) => { return { - async shouldBeVisible() { + async shouldBeVisible(iframeIndex = 0) { try { await page.waitForIdle() const webView = page.locator('.webview') @@ -11,6 +11,7 @@ export const create = ({ expect, page, VError }: CreateParams) => { await expect(webView).toHaveClass('ready') await page.waitForIdle() const childPage = await page.waitForIframe({ + iframeIndex, injectUtilityScript: false, url: /extensionId=vscode.markdown-language-features/, }) diff --git a/packages/test-worker/src/parts/ElectronApp/ElectronApp.ts b/packages/test-worker/src/parts/ElectronApp/ElectronApp.ts index 6bb54495ef..3834db54d3 100644 --- a/packages/test-worker/src/parts/ElectronApp/ElectronApp.ts +++ b/packages/test-worker/src/parts/ElectronApp/ElectronApp.ts @@ -47,13 +47,14 @@ export const create = ({ browserRpc, electronObjectId, electronRpc, firstWindow, currentSessionRpc = sessionRpc }, rpc: electronRpc, - waitForIframe({ injectUtilityScript = true, url }) { + waitForIframe({ iframeIndex = 0, injectUtilityScript = true, url }) { return WaitForIframe.waitForIframe({ browserRpc, createPage: Page.create, electronObjectId, electronRpc, idleTimeout, + iframeIndex, injectUtilityScript, sessionRpc: currentSessionRpc, url, diff --git a/packages/test-worker/src/parts/Page/Page.ts b/packages/test-worker/src/parts/Page/Page.ts index 5c54989efc..01c409cf00 100644 --- a/packages/test-worker/src/parts/Page/Page.ts +++ b/packages/test-worker/src/parts/Page/Page.ts @@ -144,13 +144,14 @@ export const create = ({ async waitForIdle() { return PageWaitForIdle.waitForIdle(this.rpc, this.electronRpc.canUseIdleCallback, idleTimeout) }, - waitForIframe({ injectUtilityScript = true, url }) { + waitForIframe({ iframeIndex = 0, injectUtilityScript = true, url }) { return WaitForIframe.waitForIframe({ browserRpc, createPage: create, electronObjectId, electronRpc, idleTimeout, + iframeIndex, injectUtilityScript, sessionRpc, url, diff --git a/packages/test-worker/src/parts/WaitForIframe/WaitForIframe.ts b/packages/test-worker/src/parts/WaitForIframe/WaitForIframe.ts index 0c37dbae48..1dc536f890 100644 --- a/packages/test-worker/src/parts/WaitForIframe/WaitForIframe.ts +++ b/packages/test-worker/src/parts/WaitForIframe/WaitForIframe.ts @@ -9,16 +9,20 @@ interface TargetInfo { readonly url: string } -const findMatchingIframe = (targets, expectedUrl) => { +const findMatchingIframe = (targets, expectedUrl, iframeIndex) => { + let matchingIndex = 0 for (const target of targets) { if (expectedUrl.test(target.url) || expectedUrl.test(target.title)) { - return target + if (matchingIndex === iframeIndex) { + return target + } + matchingIndex++ } } return undefined } -const waitForMatchingIframe = async (sessionRpc, url, timeout = 30_000) => { +const waitForMatchingIframe = async (sessionRpc, url, iframeIndex, timeout = 30_000) => { const deadline = performance.now() + timeout let targets: readonly TargetInfo[] = [] while (performance.now() < deadline) { @@ -26,6 +30,7 @@ const waitForMatchingIframe = async (sessionRpc, url, timeout = 30_000) => { const matchingIframe = findMatchingIframe( targets.filter((target) => target.type === 'iframe'), url, + iframeIndex, ) if (matchingIframe) { return matchingIframe @@ -42,6 +47,7 @@ export const waitForIframe = async ({ electronObjectId, electronRpc, idleTimeout, + iframeIndex = 0, injectUtilityScript, sessionRpc, url, @@ -53,7 +59,7 @@ export const waitForIframe = async ({ // 4. resolve promise with execution context id and frame Id, clean up listeners // TODO ask browser rpc for targets / add target change listener - const matchingIframe = await waitForMatchingIframe(sessionRpc, url) + const matchingIframe = await waitForMatchingIframe(sessionRpc, url, iframeIndex) const iframeSessionId = await DevtoolsProtocolTarget.attachToTarget(sessionRpc, { flatten: true,