Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ff4c140
Compress PR screenshot previews via JPEG and restore artifact uploads
shai-almog Oct 15, 2025
7c4555a
Fix screenshot comparison comment generation
shai-almog Oct 15, 2025
9a14e97
Fix screenshot artifact uploads and comment auth
shai-almog Oct 15, 2025
e4b7274
Fix instrumentation artifact upload and PR auth
shai-almog Oct 15, 2025
1f07854
Fix screenshot automation auth and uploads
shai-almog Oct 15, 2025
ccf1041
Adjust Actions artifact API version
shai-almog Oct 15, 2025
d55b35d
Improve screenshot previews and comment handling
shai-almog Oct 15, 2025
7846150
Fix screenshot comment attachments and multi-logcat parsing
shai-almog Oct 15, 2025
a5f5100
Emit device JPEG previews and fix screenshot comment uploads
shai-almog Oct 15, 2025
115fd12
Fix CN1SS payload extraction
shai-almog Oct 16, 2025
32bac66
Fail on comment upload errors and shrink inline previews
shai-almog Oct 16, 2025
7b25847
Fix comment length tracking without nonlocal
shai-almog Oct 16, 2025
250c005
Fix attachment upload failure tracking
shai-almog Oct 16, 2025
8428d72
Prefer download URLs for screenshot comment attachments
shai-almog Oct 16, 2025
bdc3945
Inline screenshot previews without attachment uploads
shai-almog Oct 16, 2025
004a48c
Publish Android screenshot previews via repository branch
shai-almog Oct 16, 2025
9da9688
Use CN1SS token for Android workflow
shai-almog Oct 16, 2025
3c9ae28
Adding screenshot files for validation
shai-almog Oct 16, 2025
656009d
Fixed passing test state.
shai-almog Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/scripts-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,20 @@ name: Test Android build scripts

jobs:
build-android:
permissions:
contents: read
pull-requests: write
issues: write
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Install Pillow for image processing
run: |
python3 -m pip install --upgrade pip
python3 -m pip install pillow
- name: Setup workspace
run: ./scripts/setup-workspace.sh -q -DskipTests
- name: Build Android port
Expand Down Expand Up @@ -45,4 +56,4 @@ jobs:
path: artifacts/*.png
if-no-files-found: warn
retention-days: 14
compression-level: 6
compression-level: 6
Binary file added scripts/android/screenshots/BrowserComponent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added scripts/android/screenshots/MainActivity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions scripts/android/screenshots/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Android Instrumentation Test Screenshots

This directory stores reference screenshots for Android native instrumentation tests.

Each PNG file should be named after the test stream that emits the screenshot
(e.g. `MainActivity.png` or `BrowserComponent.png`). The automation in
`scripts/run-android-instrumentation-tests.sh` compares the screenshots emitted
by the emulator with the files stored here. If the pixels differ (ignoring PNG
metadata) or if a reference image is missing, the workflow posts a pull request
comment that includes the updated screenshot.

When the comparison passes, no screenshot artifacts are published and no
comment is created.
346 changes: 288 additions & 58 deletions scripts/android/tests/HelloCodenameOneInstrumentedTest.java

Large diffs are not rendered by default.

96 changes: 76 additions & 20 deletions scripts/android/tests/cn1ss_chunk_tools.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,61 @@
#!/usr/bin/env python3
"""Helpers for extracting CN1SS chunked screenshot payloads."""
from __future__ import annotations

import argparse
import base64
import pathlib
import re
import sys
from typing import Iterable, List, Tuple
from typing import Iterable, List, Optional, Tuple

CHUNK_PATTERN = re.compile(r"CN1SS:(\d{6}):(.*)")
DEFAULT_TEST_NAME = "default"
DEFAULT_CHANNEL = ""
CHUNK_PATTERN = re.compile(
r"CN1SS(?:(?P<channel>[A-Z]+))?:(?:(?P<test>[A-Za-z0-9_.-]+):)?(?P<index>\d{6}):(?P<payload>.*)"
)


def _iter_chunk_lines(path: pathlib.Path) -> Iterable[Tuple[int, str]]:
def _iter_chunk_lines(
path: pathlib.Path,
test_filter: Optional[str] = None,
channel_filter: Optional[str] = DEFAULT_CHANNEL,
) -> Iterable[Tuple[str, int, str]]:
text = path.read_text(encoding="utf-8", errors="ignore")
for line in text.splitlines():
match = CHUNK_PATTERN.search(line)
if not match:
continue
index = int(match.group(1))
payload = re.sub(r"[^A-Za-z0-9+/=]", "", match.group(2))
test_name = match.group("test") or DEFAULT_TEST_NAME
if test_filter is not None and test_name != test_filter:
continue
channel = match.group("channel") or DEFAULT_CHANNEL
if channel_filter is not None and channel != channel_filter:
continue
index = int(match.group("index"))
payload = re.sub(r"[^A-Za-z0-9+/=]", "", match.group("payload"))
if payload:
yield index, payload
yield test_name, index, payload


def count_chunks(path: pathlib.Path) -> int:
return sum(1 for _ in _iter_chunk_lines(path))
def count_chunks(
path: pathlib.Path, test: Optional[str] = None, channel: Optional[str] = DEFAULT_CHANNEL
) -> int:
return sum(1 for _ in _iter_chunk_lines(path, test_filter=test, channel_filter=channel))


def concatenate_chunks(path: pathlib.Path) -> str:
ordered = sorted(_iter_chunk_lines(path), key=lambda item: item[0])
return "".join(payload for _, payload in ordered)
def concatenate_chunks(
path: pathlib.Path, test: Optional[str] = None, channel: Optional[str] = DEFAULT_CHANNEL
) -> str:
ordered = sorted(
_iter_chunk_lines(path, test_filter=test, channel_filter=channel),
key=lambda item: item[1],
)
return "".join(payload for _, _, payload in ordered)


def decode_chunks(path: pathlib.Path) -> bytes:
data = concatenate_chunks(path)
def decode_chunks(
path: pathlib.Path,
test: Optional[str] = None,
channel: Optional[str] = DEFAULT_CHANNEL,
) -> bytes:
data = concatenate_chunks(path, test=test, channel=channel)
if not data:
return b""
try:
Expand All @@ -43,28 +64,63 @@ def decode_chunks(path: pathlib.Path) -> bytes:
return b""


def list_tests(path: pathlib.Path) -> List[str]:
seen = {
test
for test, _, _ in _iter_chunk_lines(path, channel_filter=DEFAULT_CHANNEL)
}
return sorted(seen)


def main(argv: List[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)

p_count = subparsers.add_parser("count", help="Count CN1SS chunks in a file")
p_count.add_argument("path", type=pathlib.Path)
p_count.add_argument("--test", dest="test", default=None, help="Optional test name filter")
p_count.add_argument(
"--channel",
dest="channel",
default=DEFAULT_CHANNEL,
help="Optional channel (default=primary)",
)

p_extract = subparsers.add_parser("extract", help="Concatenate CN1SS payload chunks")
p_extract.add_argument("path", type=pathlib.Path)
p_extract.add_argument("--decode", action="store_true", help="Decode payload to binary PNG")
p_extract.add_argument("--test", dest="test", default=None, help="Test name to extract (default=unnamed)")
p_extract.add_argument(
"--channel",
dest="channel",
default=DEFAULT_CHANNEL,
help="Optional channel (default=primary)",
)

p_tests = subparsers.add_parser("tests", help="List distinct test names found in CN1SS chunks")
p_tests.add_argument("path", type=pathlib.Path)

args = parser.parse_args(argv)

if args.command == "count":
print(count_chunks(args.path))
print(count_chunks(args.path, args.test, args.channel))
return 0

if args.command == "extract":
target_test: Optional[str]
if args.test is None:
target_test = DEFAULT_TEST_NAME
else:
target_test = args.test
if args.decode:
sys.stdout.buffer.write(decode_chunks(args.path))
sys.stdout.buffer.write(decode_chunks(args.path, target_test, args.channel))
else:
sys.stdout.write(concatenate_chunks(args.path))
sys.stdout.write(concatenate_chunks(args.path, target_test, args.channel))
return 0

if args.command == "tests":
for name in list_tests(args.path):
print(name)
return 0

return 1
Expand Down
Loading
Loading