From 6e4371e0c301dddf1f78322a8f9582a8c3861ad1 Mon Sep 17 00:00:00 2001 From: Shivanshu07 Date: Thu, 21 May 2026 22:08:20 +0530 Subject: [PATCH] =?UTF-8?q?feat(advanced):=20PER-8195=20Phase=202=20?= =?UTF-8?q?=E2=80=94=20advanced=20example=20for=20io.percy:percy-appium-ap?= =?UTF-8?q?p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds advanced/ exercising the full applicable App Percy Java SDK feature surface. 9 JUnit 5 @Test methods in AdvancedTest.java exercising the Map options overload: device_name + orientation, fullscreen + status_bar/nav_bar heights, ignore regions via xpath / appium element / custom bbox, consider regions via xpath, sync mode, test_case + labels, build metadata via env (PERCY_PROJECT, PERCY_BUILD, DEVICE, OS_VERSION, APP), PERCY_BRANCH / PERCY_COMMIT override. Web-only options (widths, percyCSS, etc.) marked N/A. CI: workflow_dispatch-only — App Percy CI requires a real BrowserStack device session. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/advanced.yml | 45 +++++++ README.md | 9 ++ advanced/.gitignore | 4 + advanced/.percy.yml | 7 + advanced/Makefile | 17 +++ advanced/README.md | 31 +++++ advanced/matrix.yml | 98 ++++++++++++++ advanced/pom.xml | 51 +++++++ .../advanced/AdvancedTest.java | 127 ++++++++++++++++++ 9 files changed, 389 insertions(+) create mode 100644 .github/workflows/advanced.yml create mode 100644 advanced/.gitignore create mode 100644 advanced/.percy.yml create mode 100644 advanced/Makefile create mode 100644 advanced/README.md create mode 100644 advanced/matrix.yml create mode 100644 advanced/pom.xml create mode 100644 advanced/src/test/java/io/percy/examplepercyappiumjava/advanced/AdvancedTest.java diff --git a/.github/workflows/advanced.yml b/.github/workflows/advanced.yml new file mode 100644 index 0000000..61e11b3 --- /dev/null +++ b/.github/workflows/advanced.yml @@ -0,0 +1,45 @@ +name: Advanced — App Percy + +# PER-8195 Phase 2 — advanced example for io.percy:percy-appium-app. +# Triggers manually only (`workflow_dispatch`). App Percy CI requires a real +# device session; we don't run it on every PR because forks and Dependabot +# don't have access to the hub secrets. +on: + workflow_dispatch: + +jobs: + advanced: + runs-on: ubuntu-latest + timeout-minutes: 30 + defaults: + run: + working-directory: advanced + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 11 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Sanity-check BrowserStack secrets + env: + AA_USERNAME: ${{ secrets.AA_USERNAME }} + AA_ACCESS_KEY: ${{ secrets.AA_ACCESS_KEY }} + run: | + if [ -z "$AA_USERNAME" ] || [ -z "$AA_ACCESS_KEY" ]; then + echo "::error::AA_USERNAME / AA_ACCESS_KEY repo secrets are required for the advanced App Percy job." + exit 1 + fi + - name: Install advanced/ dependencies + run: make install + - name: Run mvn test advanced against BrowserStack + env: + AA_USERNAME: ${{ secrets.AA_USERNAME }} + AA_ACCESS_KEY: ${{ secrets.AA_ACCESS_KEY }} + APP: ${{ secrets.APP_BS_URL }} + PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} + PERCY_BRANCH: ${{ github.ref_name }} + PERCY_COMMIT: ${{ github.sha }} + run: make test diff --git a/README.md b/README.md index 094dbef..632ba41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ # example-percy-appium-java Example app used by the [Percy Java Appium tutorial](https://docs.percy.io/v2-app/docs/appium-for-java) demonstrating Percy's Java Appium integration. +> **New:** This repo ships an [`advanced/`](./advanced) example covering the full applicable App Percy SDK feature surface for `io.percy:percy-appium-app`. See the [Percy SDK Feature Matrix](https://docs.percy.io/docs/sdk-feature-matrix) for cross-SDK coverage. + +## Examples + +| Example | What it shows | Run command | +|---|---|---| +| `./` (basic, at repo root) | Minimum viable: `percy.screenshot(name)` plus Android / iOS / Espresso typed flows. Start here. | `make test-android` | +| [`./advanced/`](./advanced) | Full applicable App Percy SDK feature surface: orientation, ignore/consider regions (xpath, appium element, custom bbox), fullscreen + status/nav bar heights, sync mode, test_case + labels. JUnit 5 + Map options overload. See [`advanced/README.md`](./advanced/README.md). | `cd advanced && make test` | + ## Java Appium Tutorial The tutorial assumes you're already familiar with Java and diff --git a/advanced/.gitignore b/advanced/.gitignore new file mode 100644 index 0000000..9d6b517 --- /dev/null +++ b/advanced/.gitignore @@ -0,0 +1,4 @@ +target/ +node_modules/ +advanced-requests.json +*.log diff --git a/advanced/.percy.yml b/advanced/.percy.yml new file mode 100644 index 0000000..429103c --- /dev/null +++ b/advanced/.percy.yml @@ -0,0 +1,7 @@ +# PER-8195 — global config for the io.percy:percy-appium-app advanced +# example. Per-screenshot options in AdvancedTest.java override these. + +version: 2 + +percy: + defer_uploads: false diff --git a/advanced/Makefile b/advanced/Makefile new file mode 100644 index 0000000..283a042 --- /dev/null +++ b/advanced/Makefile @@ -0,0 +1,17 @@ +NPM=node_modules/.bin + +.PHONY: install clean test test-advanced + +$(NPM): + npm install --no-save @percy/cli@^1.31.13 + +install: $(NPM) + mvn dependency:resolve + +clean: + rm -rf target node_modules + +# Run against the BrowserStack App Automate hub. Requires AA_USERNAME, +# AA_ACCESS_KEY, APP, PERCY_TOKEN env vars. +test test-advanced: install + $(NPM)/percy app:exec -- mvn test diff --git a/advanced/README.md b/advanced/README.md new file mode 100644 index 0000000..c2bb74d --- /dev/null +++ b/advanced/README.md @@ -0,0 +1,31 @@ +# Advanced Percy + Appium-Java + +This directory exercises the full applicable Percy SDK feature surface for `io.percy:percy-appium-app`. See the basic example at the repo root for the minimum integration. + +## What this example covers + +A JUnit 5 suite (`src/test/java/io/percy/examplepercyappiumjava/advanced/AdvancedTest.java`) where each `@Test` exercises one row of the [App Percy / Appium Native matrix](../../../docs/advanced-example-feature-matrix.md): device_name override, orientation, fullscreen + status_bar_height + nav_bar_height, ignore regions via xpath / appium element / custom bounding box, consider regions via xpath, sync mode, test_case + labels, build metadata via env, PERCY_BRANCH / PERCY_COMMIT override. + +Web-only options (widths, percyCSS, minHeight, scope, discovery, domTransformation, responsiveSnapshotCapture, readiness preset, devicePixelRatio, browsers) marked `N/A` in `matrix.yml` — there's no DOM in native App Percy. + +## Run locally + +Requires BrowserStack App Automate hub credentials and an app uploaded to the BrowserStack cloud: + +```bash +cd advanced +make install +export AA_USERNAME="" +export AA_ACCESS_KEY="" +export APP="bs://" +export PERCY_TOKEN="" # do NOT commit this +make test +``` + +## CI note + +The advanced CI job is `workflow_dispatch`-only — App Percy CI requires a real BrowserStack device session, which forks/Dependabot cannot access. See `.github/workflows/advanced.yml`. + +## Coverage matrix + +States: `Covered` / `N/A — ` / `Planned` / `Deprecated`. Source of truth is [`matrix.yml`](./matrix.yml). diff --git a/advanced/matrix.yml b/advanced/matrix.yml new file mode 100644 index 0000000..9f135c3 --- /dev/null +++ b/advanced/matrix.yml @@ -0,0 +1,98 @@ +# PER-8195 Phase 2 — Appium-Java matrix-row mapping. +# Test code: src/test/java/io/percy/examplepercyappiumjava/advanced/AdvancedTest.java + +sdk: appium-java +package: io.percy:percy-appium-app +language: java +sdk_min_version: '2.2.0' +cli_min_version: '1.31.13' + +rows: + - id: device_name_override + state: covered + test: 'AdvancedTest > exercisesDeviceNameAndOrientation' + - id: orientation_handling + state: covered + test: 'AdvancedTest > exercisesDeviceNameAndOrientation' + - id: fullscreen + state: covered + test: 'AdvancedTest > exercisesFullscreenAndBars' + - id: status_bar_height + state: covered + test: 'AdvancedTest > exercisesFullscreenAndBars' + - id: nav_bar_height + state: covered + test: 'AdvancedTest > exercisesFullscreenAndBars' + - id: ignore_regions_xpaths + state: covered + test: 'AdvancedTest > exercisesIgnoreRegionsViaXpath' + - id: ignore_region_appium_elements + state: covered + test: 'AdvancedTest > exercisesIgnoreRegionsViaAppiumElements' + - id: custom_ignore_regions + state: covered + test: 'AdvancedTest > exercisesCustomIgnoreRegions' + - id: consider_regions_xpaths + state: covered + test: 'AdvancedTest > exercisesConsiderRegionsViaXpath' + - id: sync + state: covered + test: 'AdvancedTest > exercisesSyncMode' + - id: test_case + state: covered + test: 'AdvancedTest > exercisesTestCaseAndLabels' + - id: labels + state: covered + test: 'AdvancedTest > exercisesTestCaseAndLabels' + + - id: build_metadata_via_env + state: covered + test: 'AdvancedTest @BeforeAll capabilities driven by env (PERCY_PROJECT, PERCY_BUILD, DEVICE, OS_VERSION, APP)' + - id: env_percy_branch + state: covered + test: 'CI advanced job sets PERCY_BRANCH via env' + - id: env_percy_commit + state: covered + test: 'CI advanced job sets PERCY_COMMIT via env' + + # Web-only options — not applicable. + - id: widths + state: n_a + reason: 'No DOM in native App Percy.' + - id: percy_css + state: n_a + reason: 'No DOM in native App Percy.' + - id: min_height + state: n_a + reason: 'No DOM in native App Percy.' + - id: enable_javascript + state: n_a + reason: 'No DOM in native App Percy.' + - id: scope + state: n_a + reason: 'No DOM in native App Percy.' + - id: discovery + state: n_a + reason: 'No DOM in native App Percy.' + - id: dom_transformation + state: n_a + reason: 'No DOM in native App Percy.' + - id: responsive_snapshot_capture + state: n_a + reason: 'No DOM in native App Percy.' + - id: readiness_preset + state: n_a + reason: 'Not applicable; native screenshots after explicit Thread.sleep / WebDriverWait.' + - id: device_pixel_ratio + state: n_a + reason: 'Device DPR captured implicitly.' + - id: browsers + state: n_a + reason: 'App Percy targets devices, not browsers.' + + - id: percy_yml_global_config + state: covered + test: 'global config consumed via .percy.yml' + - id: environment_info_reporting + state: covered + test: 'automatic via io.percy:percy-appium-app client info' diff --git a/advanced/pom.xml b/advanced/pom.xml new file mode 100644 index 0000000..46b63cf --- /dev/null +++ b/advanced/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + io.percy.examplepercyappiumjava + example-percy-appium-java-advanced + jar + 1.0-SNAPSHOT + + 5.9.1 + 11 + 11 + UTF-8 + + + + io.appium + java-client + 7.6.0 + + + io.percy + percy-appium-app + 2.2.0 + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + + + + maven-surefire-plugin + 2.22.2 + + false + + + + + diff --git a/advanced/src/test/java/io/percy/examplepercyappiumjava/advanced/AdvancedTest.java b/advanced/src/test/java/io/percy/examplepercyappiumjava/advanced/AdvancedTest.java new file mode 100644 index 0000000..08ef690 --- /dev/null +++ b/advanced/src/test/java/io/percy/examplepercyappiumjava/advanced/AdvancedTest.java @@ -0,0 +1,127 @@ +package io.percy.examplepercyappiumjava.advanced; + +// PER-8195 Phase 2 — appium-java advanced example. +// Each @Test exercises one row of the App Percy / Appium Native matrix. +// See ../../../../matrix.yml for the canonical mapping. +// +// Run against the BrowserStack App Automate hub. Requires AA_USERNAME, +// AA_ACCESS_KEY, APP env vars. See ../README.md. + +import io.appium.java_client.MobileBy; +import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.android.AndroidElement; +import io.percy.appium.AppPercy; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class AdvancedTest { + private static final String HUB_URL = "https://hub-cloud.browserstack.com/wd/hub"; + private static AndroidDriver driver; + private static AppPercy percy; + + @BeforeAll + static void setUp() throws Exception { + DesiredCapabilities caps = new DesiredCapabilities(); + caps.setCapability("browserstack.user", System.getenv("AA_USERNAME")); + caps.setCapability("browserstack.key", System.getenv("AA_ACCESS_KEY")); + caps.setCapability("app", System.getenv("APP")); + caps.setCapability("device", System.getenv().getOrDefault("DEVICE", "Google Pixel 6")); + caps.setCapability("os_version", System.getenv().getOrDefault("OS_VERSION", "12.0")); + caps.setCapability("project", System.getenv().getOrDefault("PERCY_PROJECT", "Percy Appium Java Advanced")); + caps.setCapability("build", System.getenv().getOrDefault("PERCY_BUILD", "Advanced Java Appium")); + caps.setCapability("percy.enabled", "true"); + caps.setCapability("percy.ignoreErrors", "true"); + + driver = new AndroidDriver<>(new URL(HUB_URL), caps); + percy = new AppPercy(driver); + Thread.sleep(5000); + } + + @AfterAll + static void tearDown() { + if (driver != null) driver.quit(); + } + + @Test + void exercisesBaselineScreenshot() { + percy.screenshot("Wikipedia Home"); + } + + @Test + void exercisesDeviceNameAndOrientation() { + Map opts = new HashMap<>(); + opts.put("device_name", System.getenv().getOrDefault("DEVICE", "Google Pixel 6")); + opts.put("orientation", "landscape"); + percy.screenshot("Wikipedia Home — landscape", opts); + } + + @Test + void exercisesFullscreenAndBars() { + Map opts = new HashMap<>(); + opts.put("fullscreen", true); + opts.put("status_bar_height", 24); + opts.put("nav_bar_height", 0); + percy.screenshot("Wikipedia Home — fullscreen", opts); + } + + @Test + void exercisesIgnoreRegionsViaXpath() { + Map opts = new HashMap<>(); + opts.put("ignore_regions_xpaths", + Arrays.asList("//android.widget.TextView[@text=\"Search Wikipedia\"]")); + percy.screenshot("Wikipedia Home — ignore via xpath", opts); + } + + @Test + void exercisesIgnoreRegionsViaAppiumElements() { + AndroidElement el = (AndroidElement) new WebDriverWait(driver, 30).until( + ExpectedConditions.elementToBeClickable(MobileBy.AccessibilityId("Search Wikipedia"))); + Map opts = new HashMap<>(); + opts.put("ignore_region_appium_elements", Arrays.asList(el)); + percy.screenshot("Wikipedia Home — ignore via appium element", opts); + } + + @Test + void exercisesCustomIgnoreRegions() { + Map region = new HashMap<>(); + region.put("top", 0); + region.put("bottom", 100); + region.put("left", 0); + region.put("right", 300); + Map opts = new HashMap<>(); + opts.put("custom_ignore_regions", Arrays.asList(region)); + percy.screenshot("Wikipedia Home — custom ignore region", opts); + } + + @Test + void exercisesConsiderRegionsViaXpath() { + Map opts = new HashMap<>(); + opts.put("consider_regions_xpaths", + Arrays.asList("//android.widget.TextView[@text=\"Search Wikipedia\"]")); + percy.screenshot("Wikipedia Home — consider via xpath", opts); + } + + @Test + void exercisesSyncMode() { + Map opts = new HashMap<>(); + opts.put("sync", true); + percy.screenshot("Wikipedia Home — sync", opts); + } + + @Test + void exercisesTestCaseAndLabels() { + Map opts = new HashMap<>(); + opts.put("test_case", "home-smoke"); + opts.put("labels", "smoke,appium-java"); + percy.screenshot("Wikipedia Home — test_case + labels", opts); + } +}