From 993c21e26790c62b0f62a3efbb3861e9f2c15b15 Mon Sep 17 00:00:00 2001 From: yashchavanweb Date: Tue, 20 Oct 2026 18:03:50 +0530 Subject: [PATCH] feat(dev): add region feature context and app automate upload --- .github/tc-context/feature-context.md | 32 ++++++++++++++++++ .github/workflows/build-apk.yml | 38 ++++++++++++++++++++++ .gitignore | 2 ++ scripts/upload-to-appautomate.js | 47 +++++++++++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 .github/tc-context/feature-context.md create mode 100644 scripts/upload-to-appautomate.js diff --git a/.github/tc-context/feature-context.md b/.github/tc-context/feature-context.md new file mode 100644 index 0000000..67e4da1 --- /dev/null +++ b/.github/tc-context/feature-context.md @@ -0,0 +1,32 @@ +# Feature context for Test Companion exploration + +Edit this file in the PR that ships the feature. The next workflow (exploration) reads it +and passes it to `tc app generate` as the exploration instructions. + +## App +BStack Bank (Android, package com.yash.bankingapp) + +## Feature under test +Region functionality. From the dashboard, a logged-in user opens the Region quick action and +the app shows the user's region details (Region Info screen with a "Region Details" section), +fetched live from the device's public IP address. + +## Where to find it in the app +1. Launch the app. On the login screen, tap the "Regular User" button to log in as a regular user. +2. On the dashboard, find the Region option in the Quick Actions. Its label shows the detected + city name, or "Region" while the location is not known yet. +3. Tap it to open the "Region Info" screen. + +(Login credentials come from your test data, never put secrets here.) + +## What to verify +- Region quick action is visible on the dashboard after logging in as a Regular User. +- Tapping it opens the Region Info screen. +- Region details are fetched and shown, including the "Region / State" row, under "Region Details". +- The refresh button reloads the details. +- If the region data cannot be fetched, the screen shows "Could not fetch region data". + +## What good test cases look like +- One user-visible behaviour per test case +- Numbered steps, each with one clear expected result +- Include happy path and key negative cases (for example no network, refresh, back navigation) diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index 3f4d15b..aeaf088 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -11,6 +11,10 @@ concurrency: jobs: build: + outputs: + bs_url: ${{ steps.appautomate.outputs.bs_url }} + custom_id: ${{ steps.appautomate.outputs.custom_id }} + apk_name: ${{ steps.apk.outputs.name }} # Only when: PR was MERGED, from branch pr/workflow, and title starts with "feat(dev): " if: >- github.event.pull_request.merged == true && @@ -78,12 +82,46 @@ jobs: cp "$SRC" "$NAME" echo "name=$NAME" >> "$GITHUB_OUTPUT" + # Stable custom id: re-uploads keep the same name, so later flows can always find the latest build. + - name: Upload APK to BrowserStack App Automate + id: appautomate + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + run: node scripts/upload-to-appautomate.js "${{ steps.apk.outputs.name }}" "BStackBankLatest" + - name: Upload APK to Google Drive env: GDRIVE_SERVICE_ACCOUNT_JSON: ${{ secrets.GDRIVE_SERVICE_ACCOUNT_JSON }} GDRIVE_FOLDER_ID: ${{ secrets.GDRIVE_FOLDER_ID }} run: node scripts/upload-to-drive.js "${{ steps.apk.outputs.name }}" + # Handoff for the exploration flow: BS app id + feature context from the repo. + - name: Write handoff file + env: + BS_URL: ${{ steps.appautomate.outputs.bs_url }} + CUSTOM_ID: ${{ steps.appautomate.outputs.custom_id }} + APK_NAME: ${{ steps.apk.outputs.name }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + run: | + mkdir -p handoff + cp .github/tc-context/feature-context.md handoff/feature-context.md + node -e ' + const fs=require("fs"), e=process.env; + fs.writeFileSync("handoff/handoff.json", JSON.stringify({ + bs_url:e.BS_URL, custom_id:e.CUSTOM_ID, apk_name:e.APK_NAME, + pr_number:e.PR_NUMBER, pr_title:e.PR_TITLE, sha:e.MERGE_SHA + },null,2)+"\n");' + cat handoff/handoff.json + + - uses: actions/upload-artifact@v4 + with: + name: tc-handoff + path: handoff/ + retention-days: 7 + - uses: actions/upload-artifact@v4 with: name: ${{ steps.apk.outputs.name }} diff --git a/.gitignore b/.gitignore index 2cb3ac4..c8ee543 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ yash-demo-banking-app-firebase-adminsdk-fbsvc-a1594354a1.json # Firebase / Google config (contains API keys — inject at build time via EAS Secrets) # google-services.json GoogleService-Info.plist + +plan.md diff --git a/scripts/upload-to-appautomate.js b/scripts/upload-to-appautomate.js new file mode 100644 index 0000000..6ee11fe --- /dev/null +++ b/scripts/upload-to-appautomate.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node +// Uploads an APK to BrowserStack App Automate with a stable custom_id and prints the result. +// Usage: node scripts/upload-to-appautomate.js +// Env: BROWSERSTACK_USERNAME, BROWSERSTACK_ACCESS_KEY +// Writes GitHub Actions outputs (bs_url, custom_id, shareable_id) when GITHUB_OUTPUT is set. +// No dependencies: Node 18+ (fetch, FormData, Blob). +const fs = require('fs'); +const { Buffer } = require('buffer'); + +async function main() { + const [file, customId] = process.argv.slice(2); + if (!file || !fs.existsSync(file)) throw new Error(`APK not found: ${file}`); + if (!customId || !/^[A-Za-z0-9_-]+$/.test(customId)) + throw new Error('customId is required and may only contain letters, digits, _ and -'); + const user = process.env.BROWSERSTACK_USERNAME; + const key = process.env.BROWSERSTACK_ACCESS_KEY; + if (!user || !key) throw new Error('BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY not set'); + + const form = new FormData(); + form.append('file', new Blob([fs.readFileSync(file)]), file.split('/').pop()); + form.append('custom_id', customId); + + const res = await fetch('https://api-cloud.browserstack.com/app-automate/upload', { + method: 'POST', + headers: { Authorization: 'Basic ' + Buffer.from(`${user}:${key}`).toString('base64') }, + body: form, + }); + const text = await res.text(); + if (!res.ok) throw new Error(`App Automate upload failed: ${res.status} ${text}`); + const out = JSON.parse(text); + if (!out.app_url) throw new Error(`No app_url in response: ${text}`); + + console.log(`Uploaded to App Automate: ${out.app_url} (custom_id: ${out.custom_id})`); + if (out.shareable_id) console.log(`Shareable id: ${out.shareable_id}`); + + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync( + process.env.GITHUB_OUTPUT, + `bs_url=${out.app_url}\ncustom_id=${out.custom_id || customId}\nshareable_id=${out.shareable_id || ''}\n` + ); + } +} + +main().catch((e) => { + console.error(e.message); + process.exit(1); +});