Skip to content

feat: black-box E2E tests (Appium + UiAutomator2) + accessibility instrumentation#23

Merged
timbortnik merged 12 commits into
mainfrom
appium-uiautomator2-e2e
Jun 14, 2026
Merged

feat: black-box E2E tests (Appium + UiAutomator2) + accessibility instrumentation#23
timbortnik merged 12 commits into
mainfrom
appium-uiautomator2-e2e

Conversation

@timbortnik

@timbortnik timbortnik commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Add an end-to-end UI test harness that drives the installed APK on an Android emulator, plus the Semantics instrumentation that makes the app both testable and accessible (the two are the same work — UiAutomator2 reads the a11y tree).

App (lib/):

  • a11y_ids.dart: stable Semantics identifiers (surface to Android as resource-id), the locale-independent locator contract shared with the tests.
  • home_screen.dart: wrap ~20 interactive elements via a MergeSemantics helper so each is a single labeled, clickable node; enlarge the theme button and location selector to a 48dp tap target (ADA).
  • charts: a hybrid-composition PlatformView cannot be reached by a Flutter Semantics wrapper, so set the localized contentDescription on the native ImageView (SvgChartPlatformView.kt) via a new a11yLabel creation param.
  • 3 new l10n keys (clearSearch, hourly/weeklyChartLabel); regenerated across all locales (English fallback until translated).

Tests (e2e/, no Flutter dependency):

  • WebdriverIO + Appium UiAutomator2, consuming a prebuilt APK by path.
  • home_happy_path.e2e.js: launch + location/theme sheet navigation.
  • accessibility.e2e.js: black-box ADA — every control labeled + >=48dp (inline attribution links size-exempt per WCAG 2.5.8). Contrast deferred.
  • uiautomator2 driver pinned to 4.2.9 (last Appium-2.x-compatible).

CI:

  • .github/workflows/e2e.yml: Flutter build stage -> Flutter-free Node/Appium test stage on a KVM x86_64 emulator (PR + manual dispatch).

Verified locally: make analyze clean, Dart + Kotlin tests pass, both specs green on an API 35 x86_64 emulator.

Summary by CodeRabbit

  • New Features
    • Added screen-reader accessible labels for charts, location/theme pickers, and city search results.
    • Introduced localized “Clear search”, and hourly (48-hour) and weekly (7-day) chart labels across supported languages.
  • Bug Fixes
    • Weekly chart labeling now consistently refers to 7 days.
    • Weather error messaging can now show a more specific failure reason.
  • Tests
    • Added Android end-to-end UI testing coverage using Appium/UiAutomator2.

…trumentation

Add an end-to-end UI test harness that drives the installed APK on an Android
emulator, plus the Semantics instrumentation that makes the app both testable
and accessible (the two are the same work — UiAutomator2 reads the a11y tree).

App (lib/):
- a11y_ids.dart: stable Semantics identifiers (surface to Android as
  resource-id), the locale-independent locator contract shared with the tests.
- home_screen.dart: wrap ~20 interactive elements via a MergeSemantics helper
  so each is a single labeled, clickable node; enlarge the theme button and
  location selector to a 48dp tap target (ADA).
- charts: a hybrid-composition PlatformView cannot be reached by a Flutter
  Semantics wrapper, so set the localized contentDescription on the native
  ImageView (SvgChartPlatformView.kt) via a new a11yLabel creation param.
- 3 new l10n keys (clearSearch, hourly/weeklyChartLabel); regenerated across
  all locales (English fallback until translated).

Tests (e2e/, no Flutter dependency):
- WebdriverIO + Appium UiAutomator2, consuming a prebuilt APK by path.
- home_happy_path.e2e.js: launch + location/theme sheet navigation.
- accessibility.e2e.js: black-box ADA — every control labeled + >=48dp
  (inline attribution links size-exempt per WCAG 2.5.8). Contrast deferred.
- uiautomator2 driver pinned to 4.2.9 (last Appium-2.x-compatible).

CI:
- .github/workflows/e2e.yml: Flutter build stage -> Flutter-free Node/Appium
  test stage on a KVM x86_64 emulator (PR + manual dispatch).

Verified locally: make analyze clean, Dart + Kotlin tests pass, both specs
green on an API 35 x86_64 emulator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@timbortnik, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 29 minutes and 33 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 063a04cd-2898-47b5-b3b8-e7c03524bf06

📥 Commits

Reviewing files that changed from the base of the PR and between 7b3a2f0 and 8cff42d.

📒 Files selected for processing (39)
  • android/app/src/main/kotlin/org/bortnik/meteogram/MainActivity.kt
  • android/app/src/main/kotlin/org/bortnik/meteogram/WeatherFetcher.kt
  • e2e/README.md
  • e2e/run-ci.sh
  • lib/l10n/app_ar.arb
  • lib/l10n/app_be.arb
  • lib/l10n/app_bg.arb
  • lib/l10n/app_bn.arb
  • lib/l10n/app_bs.arb
  • lib/l10n/app_cs.arb
  • lib/l10n/app_da.arb
  • lib/l10n/app_de.arb
  • lib/l10n/app_el.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_fi.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_hi.arb
  • lib/l10n/app_hr.arb
  • lib/l10n/app_is.arb
  • lib/l10n/app_it.arb
  • lib/l10n/app_ja.arb
  • lib/l10n/app_jv.arb
  • lib/l10n/app_ka.arb
  • lib/l10n/app_ko.arb
  • lib/l10n/app_mk.arb
  • lib/l10n/app_nl.arb
  • lib/l10n/app_no.arb
  • lib/l10n/app_pa.arb
  • lib/l10n/app_pl.arb
  • lib/l10n/app_pt.arb
  • lib/l10n/app_ro.arb
  • lib/l10n/app_sk.arb
  • lib/l10n/app_sq.arb
  • lib/l10n/app_sv.arb
  • lib/l10n/app_ta.arb
  • lib/l10n/app_tr.arb
  • lib/l10n/app_uk.arb
  • lib/l10n/app_vi.arb
  • lib/l10n/app_zh.arb
📝 Walkthrough

Walkthrough

Adds a shared accessibility identifier contract (A11yIds in Dart, mirrored in JS), wires a11yLabel through NativeSvgChartView to Android's contentDescription, annotates all interactive home screen elements with semantics identifiers, adds clearSearch/hourlyChartLabel/weeklyChartLabel to all 36 locale files, surfaces native weather-fetch error messages to the UI, and introduces a full Appium/WebdriverIO E2E harness (two spec suites, CI workflow).

Changes

Accessibility IDs, native chart labeling, UI wiring, and E2E harness

Layer / File(s) Summary
A11y ID contract (Dart + JS mirror)
lib/a11y_ids.dart, e2e/a11y_ids.js
Introduces A11yIds as the authoritative Dart class of stable accessibility identifier constants; mirrors every string value into e2e/a11y_ids.js for the test harness to consume.
Native chart contentDescription wiring
lib/widgets/native_svg_chart_view.dart, android/.../SvgChartPlatformView.kt
Adds a11yLabel to NativeSvgChartView's constructor and passes it through creationParams; the Android view reads it and sets imageView.contentDescription.
Native error reporting
android/.../WeatherFetcher.kt, android/.../MainActivity.kt, lib/services/native_svg_service.dart, lib/screens/home_screen.dart
WeatherFetcher captures fetch failure reasons (HTTP codes, exceptions) in lastFetchError; MainActivity returns that reason to Flutter; NativeSvgService exposes it for error messages shown to the user.
Localization additions
lib/l10n/app_en.arb, lib/l10n/app_*.arb (35 locales)
Adds clearSearch, hourlyChartLabel, and weeklyChartLabel keys to the English template (with ARB metadata) and all non-English locale files.
Home screen accessibility wiring
lib/screens/home_screen.dart
Introduces _identified helper; applies A11yIds to retry button, location selector, theme button, attribution links, location picker tiles, and theme picker tiles; passes localized a11yLabel to NativeSvgChartView.
E2E harness setup
e2e/package.json, e2e/wdio.conf.js, e2e/run-ci.sh, .gitignore
Creates the WebdriverIO config with Android/Appium capabilities and screenshot lifecycle hooks, the CI entrypoint shell script (Appium start, GPS fix, npm test), and ignores E2E working directories.
E2E test specs and README
e2e/specs/home_happy_path.e2e.js, e2e/specs/accessibility.e2e.js, e2e/README.md
Adds a home happy-path smoke test (chart render, location picker, theme picker) and an accessibility spec (validates content-desc labels and 48dp tap-target sizes); documents setup and CI in the README.
GitHub Actions E2E workflow
.github/workflows/e2e.yml
Builds an x86_64 debug APK with Flutter, then runs the Appium suite on an AVD-cached emulator with KVM enabled, uploading logs/artifacts unconditionally.
Comment-only 14-day → 7-day updates
android/app/src/main/AndroidManifest.xml, android/.../SvgChartGenerator.kt, android/.../MeteogramWidgetProvider.kt, android/.../meteogram_widget_weekly.xml
Updates inline comments referencing the weekly chart from "14-day" to "7-day"; no logic changes.

Sequence Diagram(s)

sequenceDiagram
    participant GHA as GitHub Actions
    participant Flutter as Flutter build
    participant Emulator as Android x86_64 Emulator
    participant RunCI as run-ci.sh
    participant Appium as Appium Server
    participant WDIO as WebdriverIO
    participant App as Meteograph APK

    GHA->>Flutter: flutter build apk --debug --target-platform android-x64
    Flutter-->>GHA: debug APK artifact
    GHA->>Emulator: launch AVD (cached snapshot)
    GHA->>RunCI: bash e2e/run-ci.sh
    RunCI->>Appium: npx appium (background)
    RunCI->>Appium: poll /status until ready
    RunCI->>Emulator: adb emu geo fix (inject GPS)
    RunCI->>WDIO: npm test
    WDIO->>Appium: connect (Appium 2, UiAutomator2)
    Appium->>Emulator: install & launch App
    WDIO->>App: find elements by content-desc / resource-id
    App-->>WDIO: element attributes (label, bounds)
    WDIO-->>GHA: test results + screenshots + artifacts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main changes: black-box E2E testing infrastructure (Appium + UiAutomator2) and accessibility instrumentation across app and test layers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch appium-uiautomator2-e2e

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

timbortnik and others added 7 commits June 13, 2026 19:32
The app fetches FORECAST_DAYS=7 (WEEKLY_FORECAST_HOURS = 7*24), so the
"weekly" chart shows 7 days. Correct the accessibility label and the stale
"14-day" references that had crept into comments across the app. The hourly
label is unchanged (still "48-hour weather forecast chart").

- weeklyChartLabel: "14-day" -> "7-day weather forecast chart"
  (regenerated across all locales).
- Fix stale 14-day comments: home_screen.dart, native_svg_service.dart,
  SvgChartGenerator.kt, AndroidManifest.xml, MeteogramWidgetProvider.kt,
  meteogram_widget_weekly.xml.
- Update the e2e weekly-chart matcher to "7-day".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
android-emulator-runner mangles multi-line inline `script:` blocks — the
appium-wait for-loop was split, so `sh -c` saw `for … do` with no `done`
and CI failed with "Syntax error: end of file unexpected". Move the
start-appium / wait / npm-test logic into e2e/run-ci.sh and invoke it as a
single line. Validated locally end-to-end against the emulator (both specs
green, exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
actions/setup-java@v4 and actions/setup-node@v4 run on Node 20, which GitHub
is deprecating (JS actions forced to Node 24 on 2026-06-16). Their v5 releases
run on node24 — confirmed via each action's action.yml `using:` field. No input
changes needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
clearSearch, hourlyChartLabel and weeklyChartLabel were added only to the
English template (app_en.arb), so gen-l10n fell back to English in every other
locale. Add real translations to all 35 app_*.arb files and regenerate. Numbers
(48 / 7) are kept as Western digits for consistency across languages.

The major languages are high-confidence; rarer ones (jv, ka, pa, bn, ta, mk,
be, sq, is, …) are best-effort and worth a native-speaker spot-check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…w CI

On a cold CI emulator there's no cached weather, so the home (success) screen
only renders after location resolves (a ~15s fallback timeout) plus the fetch
and first render. The specs clicked elements without waiting, so on the slower
CI runner the success UI wasn't present yet and clicks failed with "element
wasn't found".

- Wait for each element (waitForDisplayed, 60s) before interacting, in both
  specs' tests and before-hooks.
- run-ci.sh: `adb emu geo fix` so the app's location resolves immediately
  instead of waiting out the 15s fallback, making the success screen appear fast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a wdio afterTest hook that, on a failed test, saves a screenshot and the
UiAutomator2 page source to e2e/artifacts/, and upload that (plus appium.log) as
the `e2e-diagnostics` artifact. Makes CI failures diagnosable — e.g. shows
whether the app was stuck on the loading spinner vs an error screen. Verified
locally: a forced failure produced a PNG + XML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Screenshots were previously captured only on failure. Make them unconditional:
- afterCommand hook screenshots after each user action (tap / back / text
  entry), numbered in sequence — a shot per step of the flow.
- afterTest screenshots after every test (pass or fail), not just failures.
- page source is still captured on failure only (bulky, debug-only).
- the upload step now runs `if: always()` as the `e2e-artifacts` artifact
  (previously failure-only `e2e-diagnostics`).

Verified locally on a green run: per-step + per-test screenshots were produced
(28 PNGs for the current 8 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@timbortnik timbortnik marked this pull request as ready for review June 14, 2026 06:27
The theme-picker options now carry Semantics(selected:) reflecting the active
ThemeMode — surfaced to UiAutomator2 as the `selected` attribute and announced
by screen readers (a genuine a11y improvement for the single-select list). A new
happy-path test taps Dark then Light and asserts the selected option follows;
the afterCommand hook also screenshots each switch for visual proof.

Verified locally: selected=true/false tracks correctly (dark->true, light->false
and vice-versa); 4 happy-path + 5 accessibility specs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/widgets/native_svg_chart_view.dart (1)

73-90: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

didUpdateWidget doesn't detect a11yLabel changes.

The method checks for changes to svgString, width, and height, but not a11yLabel. If the label changes at runtime, the native contentDescription won't be updated because the native side only reads a11yLabel in init{} (SvgChartPlatformView.kt lines 74-80) and renderSvg doesn't accept it.

In practice, labels rarely change (typically only on locale changes, which trigger app restarts), but the public API accepts a parameter that won't fully work.

🔧 Proposed fix

If you want runtime label updates to work, add a11yLabel to the didUpdateWidget change detection and introduce a new method channel call to update contentDescription on the native side:

   `@override`
   void didUpdateWidget(NativeSvgChartView oldWidget) {
     super.didUpdateWidget(oldWidget);

     // Re-render if SVG or dimensions changed
     if (oldWidget.svgString != widget.svgString ||
         oldWidget.width != widget.width ||
-        oldWidget.height != widget.height) {
+        oldWidget.height != widget.height ||
+        oldWidget.a11yLabel != widget.a11yLabel) {
       _lastRenderedSvg = widget.svgString;
       if (_viewId != null && _channel != null) {
         _channel!.invokeMethod('renderSvg', {
           'svg': widget.svgString,
           'width': widget.width.round(),
           'height': widget.height.round(),
+          'a11yLabel': widget.a11yLabel,
         });
       }
       // If view not ready yet, it will render the latest SVG when created
     }
   }

Then update the Kotlin side's renderSvg handler to apply the label.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/widgets/native_svg_chart_view.dart` around lines 73 - 90, The
didUpdateWidget method in NativeSvgChartView only checks for changes to
svgString, width, and height but ignores changes to a11yLabel. To fix this, add
a11yLabel to the change detection condition (include oldWidget.a11yLabel !=
widget.a11yLabel in the if statement), and add the a11yLabel parameter to the
arguments map passed to the renderSvg method channel call. Additionally, update
the native Kotlin side's renderSvg handler to accept and apply the a11yLabel
parameter to set the contentDescription on the native view.
🧹 Nitpick comments (1)
e2e/a11y_ids.js (1)

1-26: ⚖️ Poor tradeoff

Consider automating the Dart ↔ JS contract sync.

The manual two-file mirror is documented but error-prone. If the project expands accessibility coverage, consider generating a11y_ids.js from lib/a11y_ids.dart via a build script (e.g., a Dart script that parses the source and emits JSON or JS).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/a11y_ids.js` around lines 1 - 26, The accessibility ID mapping is
currently maintained manually across two files (lib/a11y_ids.dart and
e2e/a11y_ids.js), creating synchronization risk as the project expands. Create a
build script (such as a Dart script) that parses the accessibility ID
definitions from lib/a11y_ids.dart, extracts the key-value pairs, and
automatically generates the e2e/a11y_ids.js file in the correct JavaScript
module.exports format. Integrate this script into the build process so the JS
file is regenerated whenever the Dart file changes, eliminating the need for
manual two-file synchronization and reducing the chance of inconsistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/e2e.yml:
- Line 24: Replace all tag-pinned GitHub Actions references in the e2e.yml file
with full commit SHAs to satisfy supply-chain security requirements. For each
`uses:` entry that currently uses `@v*` tags (such as `actions/checkout@v6`),
replace the tag with the corresponding full commit SHA of that action version.
This needs to be applied to all affected actions/checkout references and any
other action references using tag-based pinning throughout the workflow file.

In `@e2e/README.md`:
- Line 49: Remove the stray code fence marker (the triple backticks "```") at
line 49 in e2e/README.md. This empty unlabeled fence at the end of the file
violates markdownlint rules and can cause rendering issues. Simply delete this
line entirely to fix the markdown validation error.

In `@e2e/run-ci.sh`:
- Around line 14-17: The Appium readiness check loop (lines 14-17) attempts to
curl the /status endpoint 60 times with 1-second intervals, but if all attempts
fail, the script continues to execute npm test (line 23), which runs against a
dead server and produces misleading failures. After the readiness check loop
completes, add a validation check to ensure the curl command succeeded at least
once. If the loop completed without a successful connection, exit the script
with an appropriate error message before the npm test command executes, so the
script fails fast rather than producing confusing test failures against an
unavailable Appium server.
- Line 8: The cd command on line 8 of the run-ci.sh script does not have
explicit error handling, which means if the directory change fails, subsequent
commands will execute from an incorrect working directory. Add error handling to
the cd command (such as using || exit or || { statement; }) to ensure the script
fails immediately if the directory change fails, preventing execution of
commands from the wrong location.

In `@lib/a11y_ids.dart`:
- Line 1: The file lib/a11y_ids.dart is missing the required BSL 1.1 license
header. Add the BSL 1.1 license header at the very beginning of the file before
the existing documentation comment starting with "/// Stable accessibility
identifiers". The license header should be formatted as a Dart block comment
containing the appropriate copyright and license text.

In `@lib/l10n/app_ar.arb`:
- Around line 3-5: All non-English locale files have the three keys
(clearSearch, hourlyChartLabel, weeklyChartLabel) positioned at the top of the
file immediately after @@locale, but they must be moved to match the English
template structure. In lib/l10n/app_ar.arb (lines 3-5), lib/l10n/app_be.arb
(lines 3-5), lib/l10n/app_bg.arb (lines 3-5), lib/l10n/app_bn.arb (lines 3-5),
lib/l10n/app_bs.arb (lines 3-5), and lib/l10n/app_vi.arb (lines 3-5), remove
these three keys from their current position after @@locale and relocate them to
appear after the searchCityHint key and before the theme key in each respective
file, maintaining consistency with the English template ordering.

---

Outside diff comments:
In `@lib/widgets/native_svg_chart_view.dart`:
- Around line 73-90: The didUpdateWidget method in NativeSvgChartView only
checks for changes to svgString, width, and height but ignores changes to
a11yLabel. To fix this, add a11yLabel to the change detection condition (include
oldWidget.a11yLabel != widget.a11yLabel in the if statement), and add the
a11yLabel parameter to the arguments map passed to the renderSvg method channel
call. Additionally, update the native Kotlin side's renderSvg handler to accept
and apply the a11yLabel parameter to set the contentDescription on the native
view.

---

Nitpick comments:
In `@e2e/a11y_ids.js`:
- Around line 1-26: The accessibility ID mapping is currently maintained
manually across two files (lib/a11y_ids.dart and e2e/a11y_ids.js), creating
synchronization risk as the project expands. Create a build script (such as a
Dart script) that parses the accessibility ID definitions from
lib/a11y_ids.dart, extracts the key-value pairs, and automatically generates the
e2e/a11y_ids.js file in the correct JavaScript module.exports format. Integrate
this script into the build process so the JS file is regenerated whenever the
Dart file changes, eliminating the need for manual two-file synchronization and
reducing the chance of inconsistency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: faff3723-e7f4-483d-ada5-ab5d73ae0774

📥 Commits

Reviewing files that changed from the base of the PR and between 0a896d7 and 484e02e.

⛔ Files ignored due to path filters (38)
  • e2e/package-lock.json is excluded by !**/package-lock.json
  • lib/l10n/app_localizations.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_ar.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_be.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_bg.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_bn.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_bs.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_cs.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_da.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_de.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_el.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_en.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_es.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_fi.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_fr.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_hi.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_hr.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_is.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_it.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_ja.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_jv.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_ka.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_ko.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_mk.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_nl.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_no.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_pa.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_pl.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_pt.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_ro.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_sk.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_sq.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_sv.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_ta.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_tr.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_uk.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_vi.dart is excluded by !lib/l10n/app_localizations*.dart
  • lib/l10n/app_localizations_zh.dart is excluded by !lib/l10n/app_localizations*.dart
📒 Files selected for processing (54)
  • .github/workflows/e2e.yml
  • .gitignore
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/kotlin/org/bortnik/meteogram/MeteogramWidgetProvider.kt
  • android/app/src/main/kotlin/org/bortnik/meteogram/SvgChartGenerator.kt
  • android/app/src/main/kotlin/org/bortnik/meteogram/SvgChartPlatformView.kt
  • android/app/src/main/res/layout/meteogram_widget_weekly.xml
  • e2e/README.md
  • e2e/a11y_ids.js
  • e2e/package.json
  • e2e/run-ci.sh
  • e2e/specs/accessibility.e2e.js
  • e2e/specs/home_happy_path.e2e.js
  • e2e/wdio.conf.js
  • lib/a11y_ids.dart
  • lib/l10n/app_ar.arb
  • lib/l10n/app_be.arb
  • lib/l10n/app_bg.arb
  • lib/l10n/app_bn.arb
  • lib/l10n/app_bs.arb
  • lib/l10n/app_cs.arb
  • lib/l10n/app_da.arb
  • lib/l10n/app_de.arb
  • lib/l10n/app_el.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_fi.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_hi.arb
  • lib/l10n/app_hr.arb
  • lib/l10n/app_is.arb
  • lib/l10n/app_it.arb
  • lib/l10n/app_ja.arb
  • lib/l10n/app_jv.arb
  • lib/l10n/app_ka.arb
  • lib/l10n/app_ko.arb
  • lib/l10n/app_mk.arb
  • lib/l10n/app_nl.arb
  • lib/l10n/app_no.arb
  • lib/l10n/app_pa.arb
  • lib/l10n/app_pl.arb
  • lib/l10n/app_pt.arb
  • lib/l10n/app_ro.arb
  • lib/l10n/app_sk.arb
  • lib/l10n/app_sq.arb
  • lib/l10n/app_sv.arb
  • lib/l10n/app_ta.arb
  • lib/l10n/app_tr.arb
  • lib/l10n/app_uk.arb
  • lib/l10n/app_vi.arb
  • lib/l10n/app_zh.arb
  • lib/screens/home_screen.dart
  • lib/services/native_svg_service.dart
  • lib/widgets/native_svg_chart_view.dart

Comment thread .github/workflows/e2e.yml
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v6

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine the workflow file
fd -t f "e2e.yml" .github/workflows/

Repository: timbortnik/widget

Length of output: 87


🏁 Script executed:

# Check the content of the workflow file to see the uses entries
cat -n .github/workflows/e2e.yml | head -120

Repository: timbortnik/widget

Length of output: 4478


Pin GitHub Actions to full commit SHAs.

These uses: entries are tag-pinned (@v*) instead of commit-pinned. That weakens supply-chain guarantees and violates strict action-pinning policy.

Also applies to lines 29, 34, 49, 69, 79, 90, 104.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 24-27: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 24-24: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e.yml at line 24, Replace all tag-pinned GitHub Actions
references in the e2e.yml file with full commit SHAs to satisfy supply-chain
security requirements. For each `uses:` entry that currently uses `@v*` tags
(such as `actions/checkout@v6`), replace the tag with the corresponding full
commit SHA of that action version. This needs to be applied to all affected
actions/checkout references and any other action references using tag-based
pinning throughout the workflow file.

Source: Linters/SAST tools

Comment thread e2e/README.md Outdated
Comment thread e2e/run-ci.sh Outdated
Comment thread e2e/run-ci.sh
Comment thread lib/a11y_ids.dart
@@ -0,0 +1,45 @@
/// Stable accessibility identifiers for interactive UI elements.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the required BSL 1.1 license header.

As per coding guidelines, all Dart source files in lib/**/*.dart must include a BSL 1.1 license header.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/a11y_ids.dart` at line 1, The file lib/a11y_ids.dart is missing the
required BSL 1.1 license header. Add the BSL 1.1 license header at the very
beginning of the file before the existing documentation comment starting with
"/// Stable accessibility identifiers". The license header should be formatted
as a Dart block comment containing the appropriate copyright and license text.

Source: Coding guidelines

Comment thread lib/l10n/app_ar.arb Outdated
timbortnik and others added 2 commits June 14, 2026 10:08
The error screen showed a generic "Failed to fetch weather data" — the HTTP
status / network error was only logged then dropped. Thread the reason through
all layers:
- WeatherFetcher records it (e.g. "HTTP 429", "UnknownHostException: ...") in a
  lastFetchError field, cleared on success.
- MainActivity returns it as the PlatformException message on FETCH_FAILED.
- NativeSvgService.fetchWeather keeps it in lastFetchError.
- _loadWeather throws it, so the on-screen _error shows the real cause.

Verified on-device (offline): the screen now reads "Exception:
UnknownHostException: Unable to resolve host ...". Also makes the CI E2E
failures diagnosable — a rate-limited fetch shows "HTTP 429" instead of a
generic message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Group the two spec files into a single wdio worker so they share one Appium
session = one app launch = one Open-Meteo fetch per job, instead of an
independent cold fetch per spec. The flaky read-timeouts have been hitting one
session's fetch while the other succeeds; with a single fetch the job only needs
that one request to land.

Verified locally: 9 specs green in one worker (~38s, one session).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/app/src/main/kotlin/org/bortnik/meteogram/WeatherFetcher.kt`:
- Around line 33-34: The `lastFetchError` property at lines 33-34 is shared
mutable state that can be overwritten by concurrent fetch requests, causing one
request's error to incorrectly overwrite another's before being reported. Remove
this global `@Volatile` property entirely and refactor the fetch mechanism to
make errors request-scoped: modify the fetch operation to return or pass the
error through a callback mechanism (such as a Result type or callback parameter)
instead of storing it as shared state. Update all locations where
`lastFetchError` is currently assigned (lines 198-199, 210, and 214-215) to
instead return or communicate errors through the new request-scoped mechanism,
and update any location that reads `lastFetchError` (such as at line 105 in
MainActivity) to obtain the error from the fetch operation's result or callback
instead of accessing the shared property.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 83b58377-b3c4-4dd2-97b6-4d10de8b8f3b

📥 Commits

Reviewing files that changed from the base of the PR and between 484e02e and 7b3a2f0.

📒 Files selected for processing (6)
  • android/app/src/main/kotlin/org/bortnik/meteogram/MainActivity.kt
  • android/app/src/main/kotlin/org/bortnik/meteogram/WeatherFetcher.kt
  • e2e/specs/home_happy_path.e2e.js
  • e2e/wdio.conf.js
  • lib/screens/home_screen.dart
  • lib/services/native_svg_service.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • e2e/wdio.conf.js
  • lib/screens/home_screen.dart

Comment thread android/app/src/main/kotlin/org/bortnik/meteogram/WeatherFetcher.kt Outdated
…ipt, docs)

- WeatherFetcher: replace the global @volatile lastFetchError with a per-call
  FetchResult(success, error); fetchFromApi now returns (JSONObject?, String?).
  Concurrent in-app + background-worker/alarm fetches can no longer clobber each
  other's error reason. MainActivity reads the returned fetchResult.error.
- l10n: move clearSearch / hourlyChartLabel / weeklyChartLabel to the template
  position (after searchCityHint, before theme) in all 35 non-English locales,
  matching app_en.arb. Order-only change; values untouched.
- e2e/run-ci.sh: fail fast (dump appium.log, kill server, exit 1) if Appium
  never reports ready, instead of running npm test against a dead server;
  guard the working-dir change with `cd ... || exit 1`.
- e2e/README.md: remove stray trailing code fence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@timbortnik timbortnik merged commit ee3aa5a into main Jun 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant