diff --git a/.github/workflows/reusable_e2e.yaml b/.github/workflows/reusable_e2e.yaml index 65b55e5e..44cfafad 100644 --- a/.github/workflows/reusable_e2e.yaml +++ b/.github/workflows/reusable_e2e.yaml @@ -89,12 +89,28 @@ jobs: echo "$KUBE_SERVICE" echo "KUBE_SERVICE=$KUBE_SERVICE" >> "$GITHUB_ENV" + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install dependencies + run: | + pip install flask # we can't run chrome like apps in the CI, we use a virtual frame buffer: # refer: http://elementalselenium.com/tips/38-headless - name: Run vscode e2e in headless state - uses: coactions/setup-xvfb@v1 env: POD_TO_SELECT: ${{ env.POD_TO_SELECT }} KUBE_SERVICE: ${{ env.KUBE_SERVICE }} + run: | + sudo apt-get install -y xvfb x11-xserver-utils ffmpeg + /usr/bin/xvfb-run -s ":99 -auth /tmp/xvfb.auth -ac -screen 0 1920x1080x24" npm run test & + ffmpeg -loglevel panic -y -framerate 30 -f x11grab -video_size 1920x1080 -i :99 out.webm + wait -n + + # the video starts at around ~2 minutes, before that you will see a black screen + - name: Upload video + if: always() + uses: actions/upload-artifact@v3 with: - run: npm run test + name: video + path: out.webm diff --git a/.gitignore b/.gitignore index f1f80e63..21b79f18 100644 --- a/.gitignore +++ b/.gitignore @@ -8,19 +8,9 @@ __pycache__ **/.DS_Store target build/ -.mirrord ### go tests ### *.go_test_app ### VSCode ### vscode-ext/bin - -### Intellij ### -intellij-ext/.gradle -intellij-ext/.idea -intellij-ext/.qodana -intellij-ext/build -intellij-ext/libmirrord_layer.* -intellij-ext/bin -.idea/ diff --git a/changelog.d/+video.internal.md b/changelog.d/+video.internal.md new file mode 100644 index 00000000..973a178c --- /dev/null +++ b/changelog.d/+video.internal.md @@ -0,0 +1 @@ +Add video playback and bugfixes for the e2e diff --git a/src/tests/e2e.ts b/src/tests/e2e.ts index 18c1bf41..6499513d 100644 --- a/src/tests/e2e.ts +++ b/src/tests/e2e.ts @@ -1,141 +1,156 @@ import { expect } from "chai"; import { join } from "path"; -import { VSBrowser, StatusBar, TextEditor, EditorView, ActivityBar, DebugView, InputBox, DebugToolbar } from "vscode-extension-tester"; +import { VSBrowser, StatusBar, ActivityBar, DebugView, InputBox, DebugToolbar, BottomBarPanel, EditorView } from "vscode-extension-tester"; import get from "axios"; - -// This suite tests basic flow of mirroring traffic from remote pod -// - Enable mirrord -> Disable mirrord -// - Create mirrord config by pressing the gear icon -// - Set a breakpoint in the python file -// - Start debugging the python file -// - Select the pod from the QuickPick -// - Send traffic to the pod -// - Tests successfully exit if breakpoint is hit const kubeService = process.env.KUBE_SERVICE; const podToSelect = process.env.POD_TO_SELECT; -describe("mirrord sample flow test", function() { - - this.timeout(1000000); // --> mocha tests timeout - this.bail(true); // --> stop tests on first failure - let browser: VSBrowser; +/** + * This suite tests basic flow of mirroring traffic from remote pod. + * - Enable mirrord + * - Start debugging the python file + * - Select the pod from the QuickPick + * - Send traffic to the pod + * - Tests successfully exit if "GET: Request completed" is found in the terminal +*/ +describe("mirrord sample flow test", function () { - const testWorkspace = join(__dirname, '../../test-workspace'); - const fileName = "app_flask.py"; - const mirrordConfigPath = join(testWorkspace, '.mirrord/mirrord.json'); - const defaultTimeout = 10000; + this.timeout("6 minutes"); // --> mocha tests timeout + this.bail(true); // --> stop tests on first failure - before(async function() { - console.log("podToSelect: " + podToSelect); - console.log("kubeService: " + kubeService); + let browser: VSBrowser; - expect(podToSelect).to.not.be.undefined; - expect(kubeService).to.not.be.undefined; + const testWorkspace = join(__dirname, '../../test-workspace'); + const fileName = "app_flask.py"; + const defaultTimeout = 10000; // = 10 seconds - browser = VSBrowser.instance; - // need to bring the flask app in open editors - await browser.openResources(testWorkspace, join(testWorkspace, fileName)); - }); + before(async function () { + console.log("podToSelect: " + podToSelect); + console.log("kubeService: " + kubeService); - it("enable mirrord", async function() { - const statusBar = new StatusBar(); - await browser.driver.wait(async () => { - for (let button of await statusBar.getItems()) { - try { - if ((await button.getText()).startsWith('mirrord')) { - await button.click(); + expect(podToSelect).to.not.be.undefined; + expect(kubeService).to.not.be.undefined; - return true; - } - } catch (e) { console.error(`Something went wrong ${e}`); } - } - }, defaultTimeout, "mirrord `enable` button not found -- timed out"); + browser = VSBrowser.instance; + + await browser.openResources(testWorkspace, join(testWorkspace, fileName)); + await browser.waitForWorkbench(); - await browser.driver.wait(async () => { - for (let button of await statusBar.getItems()) { + const ew = new EditorView(); try { - if ((await button.getText()).startsWith('mirrord')) { - return true; - } - } catch (e) { console.error(`Something went wrong ${e}`); } - } - }, defaultTimeout, "mirrord `disable` button not found -- timed out"); - }); - - it("select pod from quickpick", async function() { - await setBreakPoint(fileName, browser, defaultTimeout); - await startDebugging(); - - const inputBox = await InputBox.create(defaultTimeout * 2); - // assertion that podToSelect is not undefined is done in "before" block - await browser.driver.wait(async () => { - if (!await inputBox.isDisplayed()) { - return false; - } - - for (const pick of await inputBox.getQuickPicks()) { - let label = await pick.getLabel(); - - if (label === podToSelect) { - return true; + await ew.closeEditor('Welcome'); + } catch (error) { + console.log("Welcome page is not displayed" + error) + // continue - Welcome page is not displayed } - - if (label === "Show Pods") { - await pick.select(); - } - } - - return false; - }, defaultTimeout * 2, "quickPick not found -- timed out"); - - await inputBox.selectQuickPick(podToSelect!); - }); - - it("wait for breakpoint to be hit", async function() { - const debugToolbar = await DebugToolbar.create(2 * defaultTimeout); - // waiting for breakpoint and sending traffic to pod are run in parallel - // however, traffic is sent after 10 seconds that we are sure the IDE is listening - // for breakpoints - await browser.driver.wait(async () => { - return await debugToolbar.isDisplayed(); - }, 2 * defaultTimeout, "debug toolbar not found -- timed out"); - - sendTrafficToPod(debugToolbar); - debugToolbar.waitForBreakPoint(); - }); + await ew.openEditor('app_flask.py'); + }); + + it("enable mirrord button", async function () { + const statusBar = new StatusBar(); + + await browser.driver.wait(async () => { + return await statusBar.isDisplayed(); + }); + + // vscode refreshes the status bar on load and there is no deterministic way but to retry to click on + // the mirrord button after an interval + await browser.driver.wait(async () => { + let retries = 0; + while (retries < 3) { + try { + for (let button of await statusBar.getItems()) { + if ((await button.getText()).startsWith('mirrord')) { + await button.click(); + return true; + } + } + } catch (e) { + if (e instanceof Error && e.name === 'StaleElementReferenceError') { + await new Promise(resolve => setTimeout(resolve, 1000)); + retries++; + } else { + throw e; + } + } + } + throw new Error('Failed to click the button after multiple attempts'); + }, defaultTimeout, "mirrord `enable` button not found -- timed out"); + }); + + it("select pod from quickpick", async function () { + await startDebugging(); + const inputBox = await InputBox.create(defaultTimeout * 2); + // assertion that podToSelect is not undefined is done in "before" block + await browser.driver.wait(async () => { + if (!await inputBox.isDisplayed()) { + return false; + } + + for (const pick of await inputBox.getQuickPicks()) { + let label = await pick.getLabel(); + + if (label === podToSelect) { + return true; + } + // to pick up the podToSelect, we need to select the "Show Pods" + // from quickpick as pods are not displayed first + if (label === "Show Pods") { + await pick.select(); + } + } + + return false; + }, defaultTimeout * 2, "quickPick not found -- timed out"); + + await inputBox.selectQuickPick(podToSelect!); + }); + + it("wait for process to write to terminal", async function () { + const debugToolbar = await DebugToolbar.create(2 * defaultTimeout); + const panel = new BottomBarPanel(); + await browser.driver.wait(async () => { + return await debugToolbar.isDisplayed(); + }, 2 * defaultTimeout, "debug toolbar not found -- timed out"); + + + let terminal = await panel.openTerminalView(); + + await browser.driver.wait(async () => { + const text = await terminal.getText(); + return await terminal.isDisplayed() && text.includes("Press CTRL+C to quit"); + }, 2 * defaultTimeout, "terminal text not found -- timed out"); + + await sendTrafficToPod(); + + await browser.driver.wait(async () => { + const text = await terminal.getText(); + return text.includes("GET: Request completed"); + }, defaultTimeout, "terminal text not found -- timed out"); + + }); }); -async function sendTrafficToPod(debugToolbar: DebugToolbar) { - const response = await get(kubeService!!); - expect(response.status).to.equal(200); - expect(response.data).to.equal("OK - GET: Request completed\n"); -} -// opens and sets a breakpoint in the given file -async function setBreakPoint(fileName: string, browser: VSBrowser, timeout: number, breakPoint: number = 9) { - const editorView = new EditorView(); - await editorView.openEditor(fileName); - const currentTab = await editorView.getActiveTab(); - expect(currentTab).to.not.be.undefined; - await browser.driver.wait(async () => { - const tabTitle = await currentTab?.getTitle(); - if (tabTitle !== undefined) { - return tabTitle === fileName; - } - }, timeout, "editor tab title not found -- timed out"); - - const textEditor = new TextEditor(); - await textEditor.toggleBreakpoint(breakPoint); +/** + * sends a GET request to the pod's nodePort + */ +async function sendTrafficToPod() { + const response = await get(kubeService!!); + expect(response.status).to.equal(200); + expect(response.data).to.equal("OK - GET: Request completed\n"); } -// starts debugging the current file with the provided configuration -// debugging starts from the "Run and Debug" button in the activity bar +/** + * starts debugging the current file with the provided configuration + * debugging starts from the "Run and Debug" button in the activity bar +*/ async function startDebugging(configurationFile: string = "Python: Current File") { - const activityBar = await new ActivityBar().getViewControl("Run and Debug"); - expect(activityBar).to.not.be.undefined; - const debugView = await activityBar?.openView() as DebugView; - await debugView.selectLaunchConfiguration(configurationFile); - debugView.start(); -} + const activityBar = await new ActivityBar().getViewControl("Run and Debug"); + expect(activityBar).to.not.be.undefined; + const debugView = await activityBar?.openView() as DebugView; + await debugView.selectLaunchConfiguration(configurationFile); + await debugView.start(); +} \ No newline at end of file diff --git a/test-workspace/.mirrord/mirrord.json b/test-workspace/.mirrord/mirrord.json new file mode 100644 index 00000000..46b0a57c --- /dev/null +++ b/test-workspace/.mirrord/mirrord.json @@ -0,0 +1,10 @@ +{ + "feature": { + "network": { + "incoming": "mirror", + "outgoing": true + }, + "fs": "read", + "env": true + } +} diff --git a/test-workspace/.vscode/launch.json b/test-workspace/.vscode/launch.json index 8cd635d6..07f757d5 100644 --- a/test-workspace/.vscode/launch.json +++ b/test-workspace/.vscode/launch.json @@ -7,7 +7,10 @@ "request": "launch", "program": "${file}", "console": "integratedTerminal", - "justMyCode": true + "justMyCode": true, + "env": { + "MIRRORD_CONFIG_FILE": "${workspaceFolder}/.mirrord/mirrord.json" + } } ] } \ No newline at end of file