From 93613c97537077b26d0cbfca304eac297d951f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=81nis=20Jansons?= Date: Tue, 10 Dec 2024 21:34:36 +0200 Subject: [PATCH] Initial commit --- .github/workflows/playwright.yml | 27 ++++++ .gitignore | 19 +++++ .vscode/settings.json | 4 + README.md | 21 +++++ deno.json | 9 ++ deno.lock | 56 +++++++++++++ example.env | 9 ++ main.ts | 137 +++++++++++++++++++++++++++++++ run.sh | 3 + 9 files changed, 285 insertions(+) create mode 100644 .github/workflows/playwright.yml create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 example.env create mode 100644 main.ts create mode 100755 run.sh diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..3eb1314 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f5e9e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Distribution +dist +out +build +node_modules + +# Logs +*.log* +logs + +# Environment +.env + +# Misc +.DS_Store +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..eaf16e4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "deno.enable": true, + "deno.lint": true +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a5c69a --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# SAP BAS dev space keep-alive + +This script will start a SAP BAS dev space and keep it running. + +It uses Playwright for browser automation and Deno 2 as a runtime. + +You can use SSH port forwarding with the started dev space to access SAP proxy locally for remote system access for a true local development. + +## Setup and usage + +Copy the `example.env` to `.env` and fill in the necessary details for BAS access. + +```sh +brew install deno +deno run -A npm:playwright install +./run.sh +``` + +## Debugging + +Set `DENO_HEADLESS=false` to see the browser as the actions are taken. diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..fac189e --- /dev/null +++ b/deno.json @@ -0,0 +1,9 @@ +{ + "tasks": { + "dev": "deno run --watch main.ts" + }, + "imports": { + "@std/assert": "jsr:@std/assert@1" + }, + "nodeModulesDir": "auto" +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..f8e278d --- /dev/null +++ b/deno.lock @@ -0,0 +1,56 @@ +{ + "version": "4", + "specifiers": { + "jsr:@std/assert@1": "1.0.7", + "jsr:@std/internal@^1.0.5": "1.0.5", + "npm:@playwright/test@*": "1.48.2", + "npm:@types/node@*": "22.5.4", + "npm:playwright@*": "1.48.2" + }, + "jsr": { + "@std/assert@1.0.7": { + "integrity": "64ce9fac879e0b9f3042a89b3c3f8ccfc9c984391af19e2087513a79d73e28c3", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + } + }, + "npm": { + "@playwright/test@1.48.2": { + "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", + "dependencies": [ + "playwright" + ] + }, + "@types/node@22.5.4": { + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": [ + "undici-types" + ] + }, + "fsevents@2.3.2": { + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" + }, + "playwright-core@1.48.2": { + "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==" + }, + "playwright@1.48.2": { + "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==", + "dependencies": [ + "fsevents", + "playwright-core" + ] + }, + "undici-types@6.19.8": { + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@1" + ] + } +} diff --git a/example.env b/example.env new file mode 100644 index 0000000..12daeb6 --- /dev/null +++ b/example.env @@ -0,0 +1,9 @@ +BAS_URL=https://yolo.eu10cf.applicationstudio.cloud.sap + +BAS_PROFILE=yoloswag.accounts.ondemand.com +#BAS_PROFILE=Default Identity Provider + +BAS_USERNAME=user.name@company.lol +BAS_PASSWORD= + +BAS_DEVSPACE=dev diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..758e6c4 --- /dev/null +++ b/main.ts @@ -0,0 +1,137 @@ +import playwright from "npm:playwright"; + +const devSpace = Deno.env.get("BAS_DEVSPACE")!; +const headless = Deno.env.get("DENO_HEADLESS")! === "true" ? true : false; + +if (import.meta.main) { + + while (true) { + console.log(`Starting new loop`); + await main(devSpace); + await new Promise((resolve) => setTimeout(resolve, 3 * 1000)); + } +} + +async function main(devSpacename: string) { + const browser = await playwright.chromium.launch({ + headless: headless, + }); + const context = await browser.newContext(); + const page = await context.newPage(); + + console.log(`Logging in to BAS`); + await page.goto(Deno.env.get("BAS_URL")!); + await page.locator(`text=${Deno.env.get("BAS_PROFILE")!}`).click(); + await page + .getByLabel("Email or User Name") + .fill(Deno.env.get("BAS_USERNAME")!); + await page.getByLabel("Password").fill(Deno.env.get("BAS_PASSWORD")!); + await page.waitForTimeout(500); + await page.getByText("Keep me signed in").check(); + await page.waitForTimeout(500); + await page.getByText("Continue").click(); + await page.waitForLoadState(); + await page.waitForURL("**/index.html"); + + // Request manager page directly (without iframe) + console.log(`Loading management page`); + await page.goto(Deno.env.get("BAS_URL")! + "/workspace-manager-ui/"); + await page.waitForTimeout(1000); + console.log(`Loaded management page`); + + // const pageSource = await page.content(); + // console.log(JSON.stringify(pageSource)); + + const devSpaceEl = await findCorrectDevspace(page, devSpacename); + if (!devSpaceEl) { + throw Error(`Could not find defined devspace: ${devSpacename}`); + } + + await startDevSpace(devSpaceEl, page); + + await loadVSCode(devSpaceEl, devSpacename); + + console.log(`Waiting for 5 minutes and then starting again...`); + await page.waitForTimeout(5 * 60 * 1000); + + // Close on done + await new Promise((resolve) => { + page.on("close", resolve); // <-- add this + }); + + await context.close(); + await browser.close(); +} + +async function findCorrectDevspace(page: playwright.Page, devSpacename: string) { + const rows = await page + .locator('div.dev-spaces-row:has-text("' + devSpacename + '")') + .all(); + + let findFullStack = false; + if (rows.length > 1) { + console.log(`Found multiple "${devSpacename}" dev spaces`); + findFullStack = true; + } + + for (const el of rows) { + console.log(`Checking dev space row`); + const fullstack = await el + .locator( + 'div.left-row:has-text("Full-Stack Application Using Productivity Tools")' + ) + .isVisible(); + + if (fullstack) { + console.log(`Found the fullstack dev space row`); + } + + // Skip non-fullstack lines if they exist for that name + if (findFullStack && !fullstack) { + continue; + } + return el; + } +} + +async function startDevSpace( + devSpaceEl: playwright.Locator, + page: playwright.Page +) { + const running = await devSpaceEl + .locator('div.text-center a:has-text("RUNNING")') + .isVisible(); + + if (running) { + console.log(`Dev Space running`); + return true; + } + + const stopped = await devSpaceEl + .locator('div.text-center a:has-text("STOPPED")') + .isVisible(); + + if (stopped) { + await devSpaceEl.locator('button[id^="startButton"]').click(); + await page.waitForTimeout(5 * 1000); + } + + const stopping = await devSpaceEl + .locator('div.text-center a:has-text("STOPPING")') + .isVisible(); + + const starting = await devSpaceEl + .locator('div.text-center a:has-text("STARTING")') + .isVisible(); + + if (stopping || starting) { + await page.waitForTimeout(2 * 1000); + console.log(`Waiting for dev space to ${stopping ? "stop" : "start"}...`); + await startDevSpace(devSpaceEl, page); + } +} + +async function loadVSCode(devSpaceEl: playwright.Locator, devSpacename: string) { + console.log(`Opening VSCode dev space`); + await devSpaceEl.locator('a:has-text("' + devSpacename + '")').click(); +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..e08f635 --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +deno run --env-file=.env --allow-net --allow-read --allow-env --allow-sys --allow-write --allow-run --allow-ffi main.ts