mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 10:47:32 +00:00
core, ui: resize images in the core for sharper previews and images (#7941)
* WIP: Haskell impl of resizeImageToStrSize * WIP: add FFI export wrapper * WIP: switch ios resizeImageToStrSizeSync to backend * WIP: fix resizeImageToStrSizeSync * WIP: add JNI wrappers * Add non-base64 resize and cWriteImage * WIP: add chat_write_image * fix resizeToStr * fixes * fix syntax, raise jpeg quality back * add chatWriteImage * Fix cfArgs in tests * Use jpeg-turbo encoder * add nix pins * bump jpeg-turbo * add CI deps * add encoding funs to android lib * bump jpeg-turbo for 20.04 fix * jpeg-turbo fix for 8.10 * android fixes * Bump jpeg-turbo * set static flag for jpeg-turbo * Fix nix for aarch64/android * Fix android C api * Fix resize in android * Bump hs-libjpeg-turbo * target correct override * override android lib from android pkgs * add macos build tweaks * bump hs-jpeg-turbo for 8.10 fixes * more build fixes * make ios compile * Always use pkgconfig for libjpeg-turbo * WIP: debug libjpeg in ubuntu pkg-config CI * Add pkg-config to dockerfiles * Add libturbojpeg to dockerfiles * Use -dev version of libturbojpeg in dockerfiles * Install pkg-config in windows CI * Disable flag for custom libturbojpeg linking options Workarounds relocation error on x86_64-linux with gcc: /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libturbojpeg.a(turbojpeg.c.o): relocation R_X86_64_TPOFF32 against `errStr' can not be used when making a shared object; recompile with -fPIC /usr/bin/ld: failed to set dynamic section sizes: bad value * WIP: debug libjpeg in windows pkg-config CI * WIP: debug libjpeg package in windows CI * core: image resize module and buffer FFI * ci: link libjpeg-turbo statically on all platforms * ui: resize images in the core * core: rename image resize functions to chat_resize_image and chat_resize_image_uri * ci: restore whitespace in build.yml * ci: build libjpeg-turbo 3.1.4.1 from a submodule * core: hs-jpeg-turbo from github.com/simplex-chat * nodejs: resize images * python: resize images * core: hs-jpeg-turbo from gitlab.com, the GitHub import lacks LFS objects * core: generate /img previews * ci: link libjpeg-turbo on Windows without pkg-config * ci: build libjpeg-turbo on Windows with the GHC toolchain * docs: plan for image resizing in the core * docs: update image resizing plan * ios: prevent crash when wallpaper image cannot be resized * android, desktop: raise an error when an image cannot be resized * docs: update spec references for image resizing * docker: build the CLI image from a clone without submodules * docs: apply review fixes to image resizing plan * docker: build the CLI from a git worktree with the submodule present * docs: apply second review to image resizing plan * docs: point image resizing plan at the rewritten commit * core: choose image format by transparency in chat_resize_image * nodejs: resize images to the app sizes * python: resize images to the app sizes * nodejs, python: describe the core modules as used for image resizing * docs: add license notices of the image libraries * ci: describe the avatar method without attributing it to the apps * ci, ios, nix: remove the libjpeg-turbo build outside cabal * python: name resize_image in the core module text * nodejs, python: describe resizing to the app image sizes * core: refuse images larger than the supported size * core: resize to JPEG images with a side above 65500 pixels * docs: record image resizing decisions for the bundled libjpeg-turbo * core, docs: build libjpeg-turbo and stb from the fork submodules * docs: record the fork pins in the image resizing plan * core: resize images to at most 4320 pixels per side, refuse above 16384 * docs: record the image side limits in the image resizing plan * core: pin the rebased hs-jpeg-turbo fork * core: pin the hs-jpeg-turbo fork without package.yaml * docs: plan scaled JPEG decoding and EXIF orientation of images * docs: copy the JuicyPixels-stbir licence of the pinned fork * core: pin the hs-jpeg-turbo fork with scaled JPEG decoding * core: decode JPEG at the display size, up to 65500 pixels per side * core: rotate JPEG images by their EXIF orientation * docs: refer the image plan to the JPEG decoding code * docs: record the approved image processing decisions * core: limit JPEG decoding memory to 1024 MB outside iOS and Android * docs: name JPEG decoding in the libjpeg-turbo notice * docs: plan image resizing from files and the decisions of 26 Oct * core: scale images in one Catmull-Rom pass with sharpening * docs: plan image sizes at a fixed quality * core: resize images at a fixed quality to fill the size limit * core: resize images from a file path or a file descriptor * ios, android, desktop: resize picked images from their files * core: encode resized images with mozjpeg * tests: check descriptor closing by the file it refers to * core: raise JPEG quality only for a full-size image below 97% of the limit * ci: name the mozjpeg submodule in the Windows cleanup comments * docs: refer the image plan to the current code * docs: refer the image plan to the code merged with master * core: export every image resizing function from each library * core: close the file descriptor when its handle cannot be created * docs: count the C sources of mozjpeg with the arithmetic decoder * core: use the reviewed hs-jpeg-turbo and JuicyPixels-stbir forks * ci: build the armv7a Android core library on request * core: round image heights half up in the size search * ios: describe the file image resizing functions in SimpleX.h * ci: pick, preview and send images in the Claude app tests * core: decode PNG images with libspng and refuse other formats * core: encode PNG with libspng, keeping the smaller filter choice * ci: pick, preview and send images in the scripted app smoke tests * flake: build the PNG decoder package in nix builds * ios: remove the copy of a picked image after sending it * core: refuse image files larger than the decode memory limit * core: scale a JPEG image to its largest size once per resize * core: bisect JPEG quality without a step function * core: reduce the memory used to sharpen and to decode PNG images * android, desktop: pass the image format to the core as an integer * android, desktop: fuse the preview conditions of picked media * android, desktop, ci: check that the core resized the smoke test images * python: remove the docstrings of the explicit image resizing functions * docs: add the second review fixes to the image plan * ci: tap the iOS attachment button by its accessibility label * core: keep the colour profiles of resized images * docs: add the colour profiles to the image plan * ci: wait up to 30 s for the new group screens in the iOS smoke test * ci: pick the Android smoke test images from their own photo folders * ci: tap the iOS photo picker by position, as its tree is not shown * ci: pick the test card and the logo by their iOS picker positions * core: update JuicyPixels-stbir with the stb_image_resize licence terms * core: write a fixed Display P3 profile for Display P3 images only * core: do not keep the input image in memory for its colour profile * core: read the JPEG segments once for the orientation and the profile * core: read an image file into one buffer of its size * docs: add the third review fixes to the image plan * core: resize images with Display P3 or no profile, not profile bytes * ci: check the pixel sizes of the images sent in the iOS smoke test * core: update hs-jpeg-turbo with PNG chunk limits and encoder fixes * docs: list Compact ICC Profiles, libspng and miniz in the dependencies * android, desktop: reword the picked media preview comment * docs: refer the image plan to the code merged with master * ci: render the iOS snapshots before the images case * ci: dismiss the snapshot tests' camera alert before the iOS images case * core: update hs-jpeg-turbo with tests and the JuicyPixels-spng synopsis * core: pin hs-jpeg-turbo and JuicyPixels-stbir in simplex-chat repositories * update ios core library * core, ui: accept only JPEG and PNG image data * core: update JuicyPixels-stbir * docs: plan for the iOS copy of picked images --------- Co-authored-by: IC Rainbow <aenor.realm@gmail.com> Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> Co-authored-by: Alexandre Esteves <alexfmpe@proton.me>
This commit is contained in:
118 files changed
+4227
-348
No files matched your search
@@ -155,6 +155,7 @@ Pushes start none of the app workflows: a run is started by hand, on any branch,
|
||||
| `claude` | scripted test, then Claude | scripted test, then Claude | scripted test, then Claude |
|
||||
| `backend` | also the test backend | also the test backend | also the test backend |
|
||||
| `release` | — | also the latest release's onboarding, to tell a branch's failure from the runner's | also the latest release's onboarding, to tell a branch's failure from the runner's |
|
||||
| `armv7a` | — | — | also the core library for 32-bit Arm (armeabi-v7a), built only |
|
||||
|
||||
In the Actions tab: `ios-sim`, `desktop-linux` or `android-emu` → Run workflow, choose the branch and tick the inputs. With the GitHub CLI, each input is a field:
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## Running it
|
||||
|
||||
The workflow runs only when started by hand, on any branch: in the Actions tab, `android-emu` → Run workflow, or `gh workflow run android-emu.yml --ref <branch>` with `-f claude=true`, `-f backend=true` or `-f release=true`. [CI on GitHub's runners](../README.md) lists the inputs. A newer run on the same branch cancels the older one.
|
||||
The workflow runs only when started by hand, on any branch: in the Actions tab, `android-emu` → Run workflow, or `gh workflow run android-emu.yml --ref <branch>` with `-f claude=true`, `-f backend=true`, `-f release=true` or `-f armv7a=true`. [CI on GitHub's runners](../README.md) lists the inputs. A newer run on the same branch cancels the older one.
|
||||
|
||||
Every run builds the core library, and the backend when it's asked for, from the branch's own Haskell code. It reuses an earlier build only when the sources are unchanged, as an artifact of the same branch named after a hash of its sources (30 days): for the core `src/**`, `cabal.project`, `simplex-chat.cabal`, `PRIVACY.md`, the Nix files (`flake.nix`, `flake.lock`, `scripts/nix/**`) and `build-core.sh`; the backend is desktop-linux's, under its name and hash, so a backend either workflow built is reused by both. The workflow saves no Actions caches, which this repository evicts within hours and shares with `build.yml`.
|
||||
|
||||
@@ -18,6 +18,7 @@ Every run builds the core library, and the backend when it's asked for, from the
|
||||
| `claude` (opt-in) | after `app` passed: its APK in a new emulator at the first screen, then Claude Code drives it | about 5 min, Claude 2.5 |
|
||||
| `backend` (opt-in) | desktop-linux's `build-backend.sh` | about 30 min, skipped when reused |
|
||||
| `release` (opt-in) | the latest release's arm64 APK, its signer checked against SimpleX's key, onboarding only | about 4 min |
|
||||
| `core-armv7a` (opt-in) | `build-core.sh` with `armv7a`: `lib:simplex-chat` and `lib:support` for armv7a-android, with GHC 8.10.7; the app and the emulator use only arm64 | not measured yet |
|
||||
|
||||
Results are artifacts: `android-smoke-test` (numbered screenshots, the accessibility tree and the text of each, `steps.log`, `checks.txt`, logcat as `app.log`, `emulator.log`), `android-release-smoke-test`, `android-claude-test`, and the backend's logs; `android-app`, the APK for manual tests and the Claude job, is kept 14 days. The job summaries list the checks and the Claude usage.
|
||||
|
||||
@@ -28,7 +29,7 @@ A Google APIs x86_64 system image of API 34 (revision 14) with KVM, on the x86_6
|
||||
## Scripts
|
||||
|
||||
- `ui`: the UI driver, with desktop's command names: text and positions from uiautomator's accessibility tree, taps and keys with adb. `ui` alone prints its commands: start (with app settings), shot, text, tree, find, wait, click, click-text, scroll, type, key, layout (the `OneHandUI` preference and a restart), relaunch, text-size, do, stop. `screen-tree.py` reads the tree; `prefs.py` writes the settings, which only a debug build accepts (`run-as`).
|
||||
- `smoke-test.sh <apk> <out dir> [start|onboarding|layouts|backend]`: the scripted cases; the default is onboarding, a local group and both layouts. Onboarding on Android includes the app's instant notifications alert and the system's battery dialog. `backend` also adds the test SMP server under Your servers and checks that it passes Test servers.
|
||||
- `smoke-test.sh <apk> <out dir> [start|onboarding|layouts|backend]`: the scripted cases; the default is onboarding, a local group, both layouts, and `tests/fixtures/test.jpg` and `logo-large-rgba.png` pushed to Pictures, picked in the system's picker, resized by the core, previewed and sent in the group. Onboarding on Android includes the app's instant notifications alert and the system's battery dialog. `backend` also adds the test SMP server under Your servers and checks that it passes Test servers.
|
||||
- `install-sdk.sh`, `emulator.sh`: the emulator, above.
|
||||
- `build-core.sh`, `install-core.sh`, `release-apk.sh`: builds and the release.
|
||||
- `claude-test.md` (the prompt) and `claude-scope` (whether it uses the test backend): the Claude job, which runs desktop-linux's `claude-test.sh`, `claude-guard.py` and `claude-usage.py`.
|
||||
@@ -39,7 +40,7 @@ Desktop-linux's `backend.sh` starts it on the runner as it does for desktop: [it
|
||||
|
||||
## The Claude job
|
||||
|
||||
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. Which branches may use it is decided only by that environment's branch rule in the repository settings: on a branch the rule doesn't allow, a requested Claude run fails at the environment check. It runs once the app job has passed, on that job's APK in a new emulator, one per branch at a time. The token goes only to the step that runs `claude`; `claude-test.sh` passes it to the `claude` process alone, and every `ui` call unsets it too; it never reaches the emulator. The job starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since this prompt doesn't use the backend. The job installs Claude Code at the version the other app workflows pin, and runs `claude-haiku-5-5` with desktop's harness: `--max-turns 60`, `--max-budget-usd 3`, Bash limited to single `ui` commands by `claude-guard.py`, file tools limited to the session's directory, and its output checked for the token before anything is printed or uploaded. It passes when Claude's final message ends with `CLAUDE-TEST: PASS`.
|
||||
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. Which branches may use it is decided only by that environment's branch rule in the repository settings: on a branch the rule doesn't allow, a requested Claude run fails at the environment check. It runs once the app job has passed, on that job's APK in a new emulator, one per branch at a time. Before Claude starts, the job pushes two images from `tests/fixtures` to the emulator's Pictures, for the prompt's image part. The token goes only to the step that runs `claude`; `claude-test.sh` passes it to the `claude` process alone, and every `ui` call unsets it too; it never reaches the emulator. The job starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since this prompt doesn't use the backend. The job installs Claude Code at the version the other app workflows pin, and runs `claude-haiku-5-5` with desktop's harness: `--max-turns 90`, `--max-budget-usd 3`, Bash limited to single `ui` commands by `claude-guard.py`, file tools limited to the session's directory, and its output checked for the token before anything is printed or uploaded. It passes when Claude's final message ends with `CLAUDE-TEST: PASS`.
|
||||
|
||||
## Known limits
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Builds the arm64 Android core (libsimplex.so with the libraries it needs, and libsupport.so) with Nix, as
|
||||
# Builds the aarch64 or armv7a Android core (libsimplex.so with the libraries it needs, and libsupport.so) with Nix, as
|
||||
# scripts/android/build-android.sh does, and packs them as scripts/android/download-libs.sh lays them out.
|
||||
# Usage: scripts/ci/android/build-core.sh <output .tar.gz>
|
||||
# Usage: scripts/ci/android/build-core.sh <output .tar.gz> [aarch64|armv7a]
|
||||
# Installs the Nix version that build-android.sh pins, in single-user mode, with the zw3rk Hydra's cache, which
|
||||
# builds these jobs for master: without it, the toolchain and every dependency would be compiled too.
|
||||
|
||||
@@ -10,7 +10,8 @@ set -euo pipefail
|
||||
|
||||
nix_ver=nix-2.22.0
|
||||
nix_hash=4fed7db867186c01ce2a2077da4a6950ed16232efbf78d0cd19700cff80559f9
|
||||
jobs=(".#hydraJobs.x86_64-linux.aarch64-android:lib:simplex-chat" ".#hydraJobs.x86_64-linux.aarch64-android:lib:support")
|
||||
arch=${2:-aarch64}
|
||||
jobs=(".#hydraJobs.x86_64-linux.$arch-android:lib:simplex-chat" ".#hydraJobs.x86_64-linux.$arch-android:lib:support")
|
||||
|
||||
install_nix() {
|
||||
local tmp
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Test: onboarding and the chat list in the SimpleX Chat Android app
|
||||
# Test: onboarding, the chat list and sending images in the SimpleX Chat Android app
|
||||
|
||||
You are testing the SimpleX Chat Android app, which runs in an Android emulator on this machine. Drive it only with the `ui` helper, through Bash, one command per call: `ui <command> [args]`. Anything else is refused; don't look for ways around that, and if something blocks the test, report it. The commands are listed at the end.
|
||||
|
||||
@@ -9,6 +9,7 @@ Your working directory is where your output goes. `ui shot <name>` saves `screen
|
||||
- The app is already running, on its first screen, with a new, empty database: never run `ui start` or `ui stop`. Begin with `ui shot start` to see it.
|
||||
- `ui type` types into the field that has the focus: tap a field before typing into it. `ui key BACK` is Android's back button.
|
||||
- Besides the chats, the chat list shows the app's own cards as list items until they are closed: "New 1-time link / Paste link / Scan" (once there is a conversation), "Bottom bar / Top bar", and "Invest in SimpleX Chat!". They are part of the list, not something drawn over it: leave them open.
|
||||
- The emulator's Pictures folder has two images for part D: `test-card.jpg`, a TV test card 1280×720 (colour bars, a large circle, "1080" at the top and "TVB" at the bottom), and `simplex-logo.png`, the dark blue and turquoise SimpleX logo on a transparent background.
|
||||
|
||||
## The test
|
||||
|
||||
@@ -38,7 +39,15 @@ Your working directory is where your output goes. `ui shot <name>` saves `screen
|
||||
- the search field is in the top half of the screen (y under 1200), and "Add list" is just below it;
|
||||
- nothing is cut off, overlapping or garbled. "Start new chat" is now a floating button at the bottom right, drawn over whatever the list shows there: note what it covers, but that alone is not a failure.
|
||||
|
||||
You have at most 60 turns, and the whole test fits in about 25 if you chain steps whose outcome you can predict with `ui do` and look at the screen after each chain. For example:
|
||||
**D. Sending images in "seeded notes".**
|
||||
|
||||
1. Click "seeded notes" to open the group. Click "Attach" (left of the message field), then "Image". Android opens a picker that is not part of the app (the photo picker, or Files): find `test-card.jpg` (under Pictures, Images or Recent; its thumbnail shows the test card) and choose it, then confirm the choice if the picker has a button for it ("Add", "Select", "Done").
|
||||
2. Back in the app, the compose area shows the image above the message field before it is sent. Take a shot. Check: the preview shows the whole test card, upright, not stretched.
|
||||
3. Click "Send Message". Check: the chat shows the sent test card. Click the image: it opens full screen, and "1080" and "TVB" are sharp and readable. Take a shot, then `ui key BACK`.
|
||||
4. Repeat 1 to 3 with `simplex-logo.png`. Check: the preview and the sent image show the whole logo, and the area around its letters shows the chat's background, not black or grey.
|
||||
5. Check that the chat shows both images after `ui relaunch` and opening "seeded notes" again.
|
||||
|
||||
You have at most 90 turns, and the whole test fits in about 45 if you chain steps whose outcome you can predict with `ui do` and look at the screen after each chain. For example:
|
||||
|
||||
- `ui do click-text "Get started" -- click-text "Enter profile name" 30 -- type citester -- shot profile`
|
||||
- `ui do click-text "Create profile" -- wait Continue 60 -- shot your-network`
|
||||
@@ -49,6 +58,6 @@ You have at most 60 turns, and the whole test fits in about 25 if you chain step
|
||||
|
||||
## Report
|
||||
|
||||
Write `report.md` in your working directory: for each of A, B and C, each check you ran as PASS or FAIL with its evidence, and anything else unexpected. End your final message with one line: `CLAUDE-TEST: PASS` if every check you ran passed, otherwise `CLAUDE-TEST: FAIL` followed by the checks that failed.
|
||||
Write `report.md` in your working directory: for each of A, B, C and D, each check you ran as PASS or FAIL with its evidence, and anything else unexpected. End your final message with one line: `CLAUDE-TEST: PASS` if every check you ran passed, otherwise `CLAUDE-TEST: FAIL` followed by the checks that failed.
|
||||
|
||||
## The ui helper
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Installs the APK in the booted emulator (emulator.sh) and runs the scripted UI cases with the ui helper: onboarding to
|
||||
# the chat list, a local group, then the chat list with the reachable toolbar (the default) and with the top bar.
|
||||
# the chat list, a local group, then the chat list with the reachable toolbar (the default) and with the top bar, then
|
||||
# tests/fixtures/test.jpg and logo-large-rgba.png picked in the system's image picker and sent in the group.
|
||||
# Usage: scripts/ci/android/smoke-test.sh <apk> <output dir> [start|onboarding|layouts|backend]
|
||||
# With "start" it stops at the first screen, for an agent to go on from; with "onboarding", at the chat list. "backend"
|
||||
# also adds the test SMP server (NAMES_SERVER, from backend.sh, reached through emulator.sh reverse) under Your servers
|
||||
@@ -27,6 +28,7 @@ package=chat.simplex.app
|
||||
local_chat="seeded notes"
|
||||
search_field="Search or paste SimpleX link"
|
||||
ui=$(dirname "$0")/ui
|
||||
fixtures=$(dirname "$0")/../../../tests/fixtures
|
||||
checks=$UI_OUT/checks.txt
|
||||
mkdir -p "$UI_OUT"
|
||||
: > "$checks"
|
||||
@@ -258,6 +260,62 @@ check_layout() {
|
||||
fi
|
||||
}
|
||||
|
||||
add_images() {
|
||||
adb push "$fixtures/logo-large-rgba.png" /sdcard/Pictures/simplex-logo/simplex-logo.png
|
||||
adb push "$fixtures/test.jpg" /sdcard/Pictures/test-card/test-card.jpg
|
||||
for image in simplex-logo/simplex-logo.png test-card/test-card.jpg; do
|
||||
adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d "file:///sdcard/Pictures/$image" > /dev/null
|
||||
done
|
||||
}
|
||||
|
||||
sent_files() {
|
||||
adb shell run-as "$package" ls files/app_files 2> /dev/null | grep -c "^IMG_.*\.$1\$" || true
|
||||
}
|
||||
|
||||
sent_file() {
|
||||
local deadline=$((SECONDS + 30))
|
||||
until [ "$(sent_files "$1")" -gt "$2" ]; do
|
||||
[ "$SECONDS" -lt "$deadline" ] || return 1
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
core_returned_no_image() {
|
||||
grep -c "core returned no image" "$UI_OUT/app.log" || true
|
||||
}
|
||||
|
||||
send_image() {
|
||||
local folder=$1 ext=$2 sent no_image
|
||||
sent=$(sent_files "$ext")
|
||||
no_image=$(core_returned_no_image)
|
||||
"$ui" click-text Attach 10
|
||||
"$ui" click-text Image 10
|
||||
sleep 3
|
||||
"$ui" shot "picker-$ext"
|
||||
"$ui" click-text "$folder" 30 || fail "no $folder folder in the image picker"
|
||||
sleep 2
|
||||
"$ui" shot "picker-folder-$ext"
|
||||
"$ui" click-text Photo 30 || fail "no photo in the $folder folder of the image picker"
|
||||
if ! "$ui" wait "Cancel image preview" 10; then
|
||||
"$ui" shot "picker-selected-$ext"
|
||||
"$ui" click-text Done 5 || "$ui" click-text Add 5 || true
|
||||
fi
|
||||
check "$ext: the compose area shows the picked image" "$ui" wait "Cancel image preview" 30
|
||||
"$ui" shot "preview-$ext"
|
||||
"$ui" click-text "Send Message" 10
|
||||
check "$ext: the image is sent as a .$ext file" sent_file "$ext" "$sent"
|
||||
"$ui" shot "sent-$ext"
|
||||
check "$ext: the core resized the image" [ "$(core_returned_no_image)" -eq "$no_image" ]
|
||||
}
|
||||
|
||||
send_images() {
|
||||
add_images
|
||||
"$ui" click-text "$local_chat" 10
|
||||
"$ui" wait Attach 10
|
||||
send_image test-card jpg
|
||||
send_image simplex-logo png
|
||||
}
|
||||
|
||||
# private routing to unknown servers only: the preset servers that would proxy to the test SMP server can't reach the
|
||||
# runner's 127.0.0.1; developer tools at the debug level put the app's debug log in logcat
|
||||
started=$SECONDS
|
||||
@@ -294,6 +352,7 @@ if [ "$cases" != onboarding ]; then
|
||||
check_layout reachable
|
||||
"$ui" layout top
|
||||
check_layout top
|
||||
send_images
|
||||
fi
|
||||
check "the app is running and logged no crash" app_running
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Queueing for a runner adds from under a minute to 20 minutes. Results are artifa
|
||||
## Scripts
|
||||
|
||||
- `ui`: the UI driver. Compose for Desktop has no accessibility tree on Linux, so it finds text on screenshots by OCR (tesseract) and acts with xdotool on an Xvfb display. `ui` alone prints its commands: start (with app settings), shot, text, find, wait, click, right-click, click-text, scroll, type, key, avatar (a profile image's colour), ink (a text's height), do (several in a row), stop. `screen-text.py`, `screen-colour.py` and `screen-ink.py` are its helpers.
|
||||
- `smoke-test.sh <app dir> <out dir> [start|onboarding|setup|backend]`: the scripted cases; the default is onboarding and both layouts. `setup` also creates a local group and makes the test SMP server the app's only names server; `backend` does that, connects to the seeded name, and checks both layouts.
|
||||
- `smoke-test.sh <app dir> <out dir> [start|onboarding|setup|backend]`: the scripted cases; the default is onboarding, a local group, both layouts, and `tests/fixtures/test.jpg` and `logo-large-rgba.png` picked in the file dialog, resized by the core, previewed and sent in the group. `setup` also creates a local group and makes the test SMP server the app's only names server; `backend` does that, connects to the seeded name, and checks both layouts.
|
||||
- `backend.sh start <tar.gz> <dir>` / `stop <dir>`: the test backend, below.
|
||||
- `claude-test.sh`, `claude-test.md` (the prompt), `claude-scope` (whether the prompt uses the test backend, and optionally the session's turns and budget), `claude-guard.py`, `claude-usage.py`: the Claude job.
|
||||
- `build-core.sh`, `install-core.sh`, `build-backend.sh`, `earlier-run.sh`, `release-app.sh`, `test-summary.py`: builds, reuse and reports.
|
||||
@@ -53,7 +53,7 @@ It writes the directory's address to `<dir>/directory-link` and the SMP server's
|
||||
|
||||
## The Claude job
|
||||
|
||||
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. Which branches may use it is decided only by that environment's branch rule in the repository settings: on a branch the rule doesn't allow, a requested Claude run fails at the environment check. It runs once the app job has passed, on that job's distributable, one per branch at a time. The token goes only to the step that runs `claude`; `claude-test.sh` passes it to the `claude` process alone, and every `ui` call unsets it too. The job starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since this prompt doesn't use the backend. The job installs Claude Code at the version the iOS workflow pins too, and runs `claude-haiku-5-5` with the turns and budget `claude-scope` sets (`--max-turns 60` and `--max-budget-usd 3` unless it sets `max-turns:` or `max-budget-usd:`) and the iOS sessions' other flags (`--restricted`, `--permission-prompts none`, `--effort high`); the job's time limit is 120 minutes. Its Bash runs only single `ui` commands (`claude-guard.py`, a PreToolUse hook: Claude Code runs read-only commands without asking in every permission mode). Before anything the session wrote is printed or uploaded, `claude-test.sh` checks it for the token; if it's there, the output, transcript, Claude Code's stderr and the guard log are deleted and the job fails. It passes when Claude's final message ends with `CLAUDE-TEST: PASS`; `report.md`, the transcript, the guard's log and `claude-usage.json` are in `desktop-claude-test`.
|
||||
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. Which branches may use it is decided only by that environment's branch rule in the repository settings: on a branch the rule doesn't allow, a requested Claude run fails at the environment check. It runs once the app job has passed, on that job's distributable, one per branch at a time. Before Claude starts, the job copies two images from `tests/fixtures` to `images` in Claude's working directory, for the prompt's image part. The token goes only to the step that runs `claude`; `claude-test.sh` passes it to the `claude` process alone, and every `ui` call unsets it too. The job starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since this prompt doesn't use the backend. The job installs Claude Code at the version the iOS workflow pins too, and runs `claude-haiku-5-5` with the turns and budget `claude-scope` sets (`--max-turns 60` and `--max-budget-usd 3` unless it sets `max-turns:` or `max-budget-usd:`) and the iOS sessions' other flags (`--restricted`, `--permission-prompts none`, `--effort high`); the job's time limit is 120 minutes. Its Bash runs only single `ui` commands (`claude-guard.py`, a PreToolUse hook: Claude Code runs read-only commands without asking in every permission mode). Before anything the session wrote is printed or uploaded, `claude-test.sh` checks it for the token; if it's there, the output, transcript, Claude Code's stderr and the guard log are deleted and the job fails. It passes when Claude's final message ends with `CLAUDE-TEST: PASS`; `report.md`, the transcript, the guard's log and `claude-usage.json` are in `desktop-claude-test`.
|
||||
|
||||
## Cost
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ db_query() {
|
||||
sqlite3 "$work/$1_chat.db" "$2"
|
||||
}
|
||||
|
||||
# Prints a profile image data URI made as the apps make one: JPEG at quality 85, scaled down by the square root
|
||||
# Prints a profile image data URI: JPEG at quality 85, scaled down by the square root
|
||||
# of its excess, at most halved per step, until it is at most max_image_size characters. The picture fills the
|
||||
# whole square, a gradient between two shades of one colour with a large white letter; noise keeps JPEG from
|
||||
# compressing it much, so the image ends up near the limit.
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# backend: yes when the prompt uses the test backend; a run that asks for Claude must then start it too.
|
||||
# max-turns and max-budget-usd (optional) bound the session; claude-test.sh uses 60 and 3 when they're absent.
|
||||
backend: no
|
||||
max-turns: 300
|
||||
max-turns: 330
|
||||
max-budget-usd: 5
|
||||
@@ -1,4 +1,4 @@
|
||||
# Test: channel message signing and channel moderators in the SimpleX Chat desktop app
|
||||
# Test: channel message signing, channel moderators and sending images in the SimpleX Chat desktop app
|
||||
|
||||
You are testing the SimpleX Chat desktop app, which runs on a virtual display on this machine. Drive it only with the `ui` helper, through Bash, one command per call: `ui <command> [args]`. Anything else is refused; don't look for ways around that, and if something blocks the test, report it. The commands are listed at the end.
|
||||
|
||||
@@ -9,6 +9,7 @@ Your working directory is where your output goes. `ui shot <name>` saves `screen
|
||||
- The app is already running, on its first screen, with a new, empty database: never run `ui start` or `ui stop`. Begin with `ui shot start` to see it.
|
||||
- After onboarding, the chat list is in the left pane (x 1 to 389) and chats open in the right pane. An empty chat list shows only its toolbar, with the profile picture at about 27,730 and a pencil (New chat) at about 363,730: the search field "Search or paste SimpleX link" appears once there is a chat. The toolbar is at the bottom of the left pane.
|
||||
- The channel uses the app's preset chat relays, on the internet. Creating it and joining it wait for them: wait up to 120 s there (`ui wait "<text>" 120`).
|
||||
- The folder `images` in your working directory has two images for part I: `test-card.jpg`, a TV test card 1280×720 (colour bars, a large circle, "1080" at the top and "TVB" at the bottom), and `simplex-logo.png`, the dark blue and turquoise SimpleX logo on a transparent background. You can Read them to compare with what the app shows.
|
||||
|
||||
## How to do things in the app
|
||||
|
||||
@@ -57,7 +58,14 @@ The parts build on each other: run them in this order. cisub must join only afte
|
||||
9. (B6) Right-click "contributor post" → "Moderate" → "Delete" in the alert "Delete member message?". Check: the message shows "moderated by cimod" in its place.
|
||||
10. (B7) Channel info → "Subscribers" → "cisub" → "Block for all" → "Block for all" in the alert "Block subscriber for all?". Check: cisub's info now offers "Unblock for all". Press Escape until the chat shows. Check: the chat shows "you blocked cisub".
|
||||
|
||||
You have at most 300 turns, and the whole test fits in about 150 if you chain steps whose outcome you can predict with `ui do` and look at the screen after each chain. For example:
|
||||
**I. Sending images (as citester).**
|
||||
|
||||
1. (I1) Switch to citester, and press Escape until the chat list shows. Click the pencil (about 363,730), then "Create group". Click the name field, about 40 px below the text "Enter group name", type `image notes`, and click "Create group". If "Skip inviting members" shows, click it. Check: the group "image notes" is open in the right pane.
|
||||
2. (I2) Click the attachment button, the paperclip icon at the left end of the message field at the bottom of the right pane, then "Image". A file dialog opens: click its "File Name" field, type the absolute path of `images/test-card.jpg` in your working directory, and press Return. Back in the app, the compose area shows the image above the message field before it is sent. Take a shot. Check: the preview shows the whole test card, upright, not stretched.
|
||||
3. (I3) Press Return or click the send button at the right end of the message field. Check: the chat shows the sent test card. Click the image: it opens larger, and "1080" and "TVB" are sharp and readable. Take a shot, then close the image with Escape.
|
||||
4. (I4) Repeat I2 and I3 with `images/simplex-logo.png`. Check: the preview and the sent image show the whole logo, and the area around its letters shows the chat's background, not black or grey.
|
||||
|
||||
You have at most 330 turns, and the whole test fits in about 170 if you chain steps whose outcome you can predict with `ui do` and look at the screen after each chain. For example:
|
||||
|
||||
- `ui do click-text "Create your profile" -- wait "Enter profile name" -- click-text "Enter profile name" -- type citester -- shot profile`
|
||||
- `ui do click 363 730 -- click-text "Create public channel" 10 -- wait "Channel name" -- shot new-channel`
|
||||
@@ -68,6 +76,6 @@ You have at most 300 turns, and the whole test fits in about 150 if you chain st
|
||||
|
||||
## Report
|
||||
|
||||
Write `report.md` in your working directory as you go, so a session that is cut short still leaves its results: one row per step (S1 to S3, A1 to A5, J1, J2, A6, B1 to B3, J3 to J5, B4 to B7), each check you ran as PASS or FAIL with its evidence (the text OCR read, screenshot names), and anything else unexpected. End your final message with one line: `CLAUDE-TEST: PASS` if every check you ran passed, otherwise `CLAUDE-TEST: FAIL` followed by the steps that failed.
|
||||
Write `report.md` in your working directory as you go, so a session that is cut short still leaves its results: one row per step (S1 to S3, A1 to A5, J1, J2, A6, B1 to B3, J3 to J5, B4 to B7, I1 to I4), each check you ran as PASS or FAIL with its evidence (the text OCR read, screenshot names), and anything else unexpected. End your final message with one line: `CLAUDE-TEST: PASS` if every check you ran passed, otherwise `CLAUDE-TEST: FAIL` followed by the steps that failed.
|
||||
|
||||
## The ui helper
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Starts the desktop app on a virtual display and runs the scripted UI cases with the ui helper: onboarding to the
|
||||
# chat list, a local group, then the chat list with the toolbar at the bottom (the default) and at the top.
|
||||
# chat list, a local group, then the chat list with the toolbar at the bottom (the default) and at the top, then
|
||||
# tests/fixtures/test.jpg and logo-large-rgba.png picked in the file dialog and sent in the group.
|
||||
# Usage: scripts/ci/desktop-linux/smoke-test.sh <app dir> <output dir> [start|onboarding|layouts|setup|backend]
|
||||
# With "start" it stops at the first screen, for an agent to go on from; with "onboarding", at the chat list.
|
||||
# "setup" creates the group, makes the test SMP server (backend.sh) the one that resolves SimpleX names, and stops;
|
||||
@@ -31,6 +32,12 @@ ui=$(dirname "$0")/ui
|
||||
checks=$UI_OUT/checks.txt
|
||||
# switches are at the right of the left pane, where Settings open
|
||||
switch_x=335
|
||||
fixtures=$(cd "$(dirname "$0")/../../../tests/fixtures" && pwd)
|
||||
state=${UI_STATE:-${RUNNER_TEMP:-/tmp}/ui}
|
||||
compose_y=730
|
||||
paperclip_x=415
|
||||
message_x=700
|
||||
compose_area=976x200+390+500
|
||||
mkdir -p "$UI_OUT"
|
||||
: > "$checks"
|
||||
if [ -n "${GITHUB_ENV:-}" ]; then
|
||||
@@ -310,6 +317,67 @@ connect_by_name() {
|
||||
back_to_chat_list || fail "no chat list after the name's alert"
|
||||
}
|
||||
|
||||
sent_files() {
|
||||
find "$state/data" -name "IMG_*.$1" | wc -l
|
||||
}
|
||||
|
||||
sent_file() {
|
||||
local deadline=$((SECONDS + 30))
|
||||
until [ "$(sent_files "$1")" -gt "$2" ]; do
|
||||
[ "$SECONDS" -lt "$deadline" ] || return 1
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
latest_shot() {
|
||||
find "$UI_OUT" -maxdepth 1 -name "[0-9][0-9]-$1.png" | sort | tail -n 1
|
||||
}
|
||||
|
||||
compose_changed() {
|
||||
local changed
|
||||
convert "$(latest_shot "$1")" -crop "$compose_area" +repage "$UI_OUT/compose-$1.png"
|
||||
convert "$(latest_shot "$2")" -crop "$compose_area" +repage "$UI_OUT/compose-$2.png"
|
||||
changed=$(compare -metric AE -fuzz 10% "$UI_OUT/compose-$1.png" "$UI_OUT/compose-$2.png" null: 2>&1 || true)
|
||||
changed=${changed%% *}
|
||||
echo "pixels changed above the message field: $changed"
|
||||
[ "${changed%%.*}" -gt 2000 ]
|
||||
}
|
||||
|
||||
core_returned_no_image() {
|
||||
grep -c "core returned no image" "$UI_OUT/app.log" || true
|
||||
}
|
||||
|
||||
send_image() {
|
||||
local file=$1 ext=$2 sent no_image x y
|
||||
sent=$(sent_files "$ext")
|
||||
no_image=$(core_returned_no_image)
|
||||
"$ui" shot "before-$ext"
|
||||
"$ui" click "$paperclip_x" "$compose_y"
|
||||
"$ui" click-text Image 10
|
||||
"$ui" wait "File Name" 20
|
||||
"$ui" shot "file-dialog-$ext"
|
||||
read -r x y _ < <("$ui" find "File Name")
|
||||
"$ui" click $((x + 250)) "$y"
|
||||
"$ui" type "$fixtures/$file"
|
||||
"$ui" key Return
|
||||
sleep 3
|
||||
"$ui" shot "preview-$ext"
|
||||
check "$ext: the compose area shows the picked image" compose_changed "before-$ext" "preview-$ext"
|
||||
"$ui" click "$message_x" "$compose_y"
|
||||
"$ui" key Return
|
||||
check "$ext: the image is sent as a .$ext file" sent_file "$ext" "$sent"
|
||||
"$ui" shot "sent-$ext"
|
||||
check "$ext: the core resized the image" [ "$(core_returned_no_image)" -eq "$no_image" ]
|
||||
}
|
||||
|
||||
send_images() {
|
||||
local x y
|
||||
read -r x y < <(left_pane_position "$local_chat") || fail "no \"$local_chat\" in the chat list"
|
||||
"$ui" click "$x" "$y"
|
||||
send_image test.jpg jpg
|
||||
send_image logo-large-rgba.png png
|
||||
}
|
||||
|
||||
# private routing to unknown servers only: the preset servers that would proxy name lookups can't reach the test SMP
|
||||
# server on 127.0.0.1; developer tools at the debug level put the app's debug log in app.log
|
||||
"$ui" start "$app_dir" NetworkSMPProxyMode=Unknown DeveloperTools=true LogLevel=DEBUG
|
||||
@@ -367,6 +435,7 @@ fi
|
||||
check_layout bottom
|
||||
use_top_toolbar
|
||||
check_layout top
|
||||
send_images
|
||||
|
||||
if grep -q '^FAIL' "$checks"; then
|
||||
fail "the smoke test failed checks: $(grep -c '^FAIL' "$checks") of $(grep -cE '^(PASS|FAIL)' "$checks")"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# iOS app in the simulator on GitHub's runners
|
||||
|
||||
`.github/workflows/ios-sim.yml` builds the core library and the iOS app from the branch's code on a macOS runner, starts the app in an iPhone 17 simulator, and drives it through onboarding to the chat list with a scripted smoke test. On request it also runs a Claude session that follows a device test plan, and a test backend (relay, directory, names server, contacts) for tests that need servers and peers.
|
||||
`.github/workflows/ios-sim.yml` builds the core library and the iOS app from the branch's code on a macOS runner, starts the app in an iPhone 17 simulator, and drives it through onboarding to the chat list with a scripted smoke test; the app job's smoke test (`images`) then turns off the encryption of local files, creates a group and sends `tests/fixtures/test.jpg` and `logo-large-rgba.png`, each added to the photo library, picked, resized by the core, previewed and sent, and checks the pixel size of each sent file with `sips`. On request it also runs a Claude session that follows a device test plan, and a test backend (relay, directory, names server, contacts) for tests that need servers and peers.
|
||||
|
||||
## Running it
|
||||
|
||||
@@ -22,7 +22,7 @@ The macOS jobs run on `macos-26`, the image with Xcode 26 and the iPhone 17 simu
|
||||
|
||||
## The Claude session
|
||||
|
||||
It runs in its own job, `claude`, once the app job has passed: the job installs that job's build (the artifact `ios-sim-app`, kept 14 days) in a new simulator, runs the smoke test to reach the sessions' starting state, then one session per layout listed in `claude-scope`, with Claude Code installed from npm at the version the desktop workflow pins too. Only this job references the environment `app-sim-claude`, which holds the secret `CLAUDE_CODE_OAUTH_TOKEN` (a Claude subscription token) and admits the branches its rule allows; one runs per branch at a time. Only the Claude step gets the token, and `claude-test.sh` passes it to the `claude` processes alone. The job checks out without persisting its GitHub token and installs a pinned AXe (`install-axe.sh`), since it holds the Claude token. It starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since the smoke plan doesn't use the backend.
|
||||
It runs in its own job, `claude`, once the app job has passed: the job installs that job's build (the artifact `ios-sim-app`, kept 14 days) in a new simulator, runs the smoke test to reach the sessions' starting state, adds the plan's images to the photo library (`tests/fixtures/test.jpg`, `tests/fixtures/logo-large-rgba.png`, and a HEIC of the first turned to portrait when `sips` can write HEIC), then one session per layout listed in `claude-scope`, with Claude Code installed from npm at the version the desktop workflow pins too. Only this job references the environment `app-sim-claude`, which holds the secret `CLAUDE_CODE_OAUTH_TOKEN` (a Claude subscription token) and admits the branches its rule allows; one runs per branch at a time. Only the Claude step gets the token, and `claude-test.sh` passes it to the `claude` processes alone. The job checks out without persisting its GitHub token and installs a pinned AXe (`install-axe.sh`), since it holds the Claude token. It starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since the smoke plan doesn't use the backend.
|
||||
|
||||
Each session's final message ends with `CLAUDE-TEST: PASS`, or `CLAUDE-TEST: FAIL` and the failed cases; the step fails unless every session passes.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Makes a group avatar the way the app makes profile images (resizeImageToStrSize in apps/ios/SimpleXChat/ImageUtils.swift):
|
||||
// Makes a group avatar:
|
||||
// a JPEG at quality 0.85 drawn at scale 1, shrunk by sqrt(size / 12500), at most halved per step,
|
||||
// until its data URI is at most 12500 characters. The picture is synthetic and photo-like: a gradient,
|
||||
// discs and noise, with more noise for a higher detail.
|
||||
|
||||
@@ -21,6 +21,7 @@ You are testing the SimpleX Chat iOS app in an iPhone 17 simulator (iOS 26.5, sc
|
||||
- **Joining** (the app's normal flow, from `NewChatView.swift`; an earlier session took its first step for a failure). Tapping a group or channel the profile does not know yet, from a link or a list, shows an alert with its name (and subscriber count for a channel) and "Cancel" / "Open channel" or "Open group": that is the start of joining, not a sign that the entry is known. "Open …" opens the chat, not yet joined, with "Tap Join channel" in it and a "Join channel" / "Join group" bar at the bottom; tapping that bar joins (a channel then shows "you are subscriber"; a group may stay "connecting"). A contact works the same way: "Open new chat", then the chat's connect button. An entry already opened, joined or connecting shows the same kind of alert, plus your role ("You are a subscriber", "You are a member") once joined. So each case that joins uses the entry named for it under "This session", which no earlier case has touched. Opened and joined chats appear in the chat list, and the profile is no longer "without conversations".
|
||||
- **A second profile:** open the picker, "Your chat profiles", add a profile; a new profile has no conversations. Switch back to the first profile when done.
|
||||
- **Text size:** `ui text-size accessibility-large`, and back to `ui text-size medium` afterwards. Only one phone size is available.
|
||||
- **The photo picker** ("Choose from library" in the "Attach" dialog) is iOS's, not the app's: its photos and its "Cancel" and "Add" buttons are in the tree. Photos can share a label: tap the one you mean with `ui tap-xy` at its centre, judged from a screenshot.
|
||||
- **If the app gets stuck**, `ui relaunch` restarts it (search text is cleared, everything else stays).
|
||||
- Waiting: prefer `ui wait <text> <secs>` over repeated trees; `ui wait` returns at once when the text is already shown.
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
backend: no
|
||||
model: claude-haiku-5-5
|
||||
layouts: reachable
|
||||
cases-reachable: S2, S3, A1, A2, A3, A4, A5, J1, J2, A6, B1, B2, B3, J3, J4, J5, B4, B5, B6, B7
|
||||
max-turns: 350
|
||||
cases-reachable: S2, S3, A1, A2, A3, A4, A5, J1, J2, A6, B1, B2, B3, J3, J4, J5, B4, B5, B6, B7, I1-I4
|
||||
max-turns: 390
|
||||
max-budget-usd: 5
|
||||
@@ -1,4 +1,4 @@
|
||||
# Device test plan: channel message signing and channel moderators
|
||||
# Device test plan: channel message signing, channel moderators and sending images
|
||||
|
||||
The app as the scripted smoke test leaves it: the profile "citester", without conversations. The cases run in the order of the tables and build on each other: one channel, "ci signing", owned by citester, and two more profiles in this app, "cimod" and "cisub", that join it by its link. Do not reorder them: cisub must join only after cimod has become a moderator (B2), so that cimod learns about cisub.
|
||||
|
||||
@@ -35,3 +35,14 @@ The app as the scripted smoke test leaves it: the profile "citester", without co
|
||||
| B5 | Open "cisub" in that list | cisub's info offers "Block for all" (do not tap it yet) |
|
||||
| B6 | Back in the chat, long-press "contributor post" → "Moderate" → "Delete member message?" → "Delete" | The message shows "moderated by cimod" in its place |
|
||||
| B7 | Channel info → "Subscribers" → "cisub" → "Block for all" → "Block subscriber for all?" → "Block for all" | cisub's info now offers "Unblock for all"; the chat shows "you blocked cisub" |
|
||||
|
||||
## Sending images
|
||||
|
||||
These cases run after the channel cases: first switch to citester and go back to its chat list. The photo library has, besides the simulator's own photos, the images of these cases: a TV test card 1280×720 (JPEG: colour bars, a large circle, "1080" at the top and "TVB" at the bottom), the dark blue and turquoise SimpleX logo on a transparent background (PNG), and, when the runner could make it, the same test card turned by 90° to portrait (HEIC). In the photo picker, identify each by its thumbnail. The paperclip button left of the message field opens the "Attach" dialog; the send button is the arrow at the right end of the field; find their labels with `ui tree`.
|
||||
|
||||
| ID | Do | Expect |
|
||||
|----|----|--------|
|
||||
| I1 | Tap "Compose", then "Create group"; type `image notes` in the field "Enter group name…", check the value, tap "Create group", then "Skip" if it shows; open the group's chat if it is not open | The chat of "image notes", with the message field and the paperclip button enabled |
|
||||
| I2 | Tap the paperclip button, "Choose from library", the test card's thumbnail, then the picker's "Add" button; `ui shot I2-preview`; tap the send button; tap the sent image; `ui shot I2-full`; close the image | Before sending, the compose area shows the whole test card, upright and not stretched; after sending, the chat shows it; opened, it fills the screen width and "1080" and "TVB" are sharp and readable |
|
||||
| I3 | As I2, with the SimpleX logo | The preview and the sent image show the whole logo; the area around its letters shows the chat's background, not black or grey |
|
||||
| I4 | As I2, with the portrait test card; skipped when the library has no portrait test card | The preview and the sent image show the test card upright in portrait, as the picker's thumbnail does |
|
||||
@@ -4,7 +4,9 @@
|
||||
# chat list with the one-hand UI (the default), then the chat list again with the top bar. It leaves the app in the one-hand UI.
|
||||
# For an app in the derived data of a build, it then renders the previews of the SimpleX Snapshots tests to the snapshots
|
||||
# folder of the output dir (snapshots.sh).
|
||||
# Usage: scripts/ci/ios-sim/smoke-test.sh <path to SimpleX.app> <output dir>
|
||||
# With "images" it then turns off the encryption of local files, creates a group and sends tests/fixtures/test.jpg and
|
||||
# logo-large-rgba.png picked in the photo library, and checks the pixel sizes of the sent files.
|
||||
# Usage: scripts/ci/ios-sim/smoke-test.sh <path to SimpleX.app> <output dir> [images]
|
||||
# Each step saves a screenshot and an accessibility tree to the output dir and prints the tree to the log.
|
||||
# Later steps of the job get SIM_UDID and UI_OUT, with the simulator still running.
|
||||
|
||||
@@ -12,8 +14,15 @@ set -euo pipefail
|
||||
|
||||
app=$1
|
||||
export UI_OUT=$2
|
||||
cases=${3:-}
|
||||
bundle_id=chat.simplex.app
|
||||
ui=$(dirname "$0")/ui
|
||||
fixtures=$(cd "$(dirname "$0")/../../../tests/fixtures" && pwd)
|
||||
test_card_x=66
|
||||
logo_x=200
|
||||
photo_y=412
|
||||
picker_done_x=363
|
||||
picker_done_y=144
|
||||
mkdir -p "$UI_OUT"
|
||||
|
||||
axe --version
|
||||
@@ -87,6 +96,80 @@ expect_chat_list() {
|
||||
echo "in the $1 layout the chat list shows the onboarding pages and the toolbar"
|
||||
}
|
||||
|
||||
tap_element() {
|
||||
local line x y w h
|
||||
line=$("$ui" tree | grep -iE "^$1 \"$2\"" | tail -n 1)
|
||||
[ -n "$line" ] || return 1
|
||||
read -r x y w h < <(sed -E 's/.* at (-?[0-9]+),(-?[0-9]+) size ([0-9]+)x([0-9]+).*/\1 \2 \3 \4/' <<< "$line")
|
||||
"$ui" tap-xy $((x + w / 2)) $((y + h / 2))
|
||||
}
|
||||
|
||||
sent_files() {
|
||||
find "$(xcrun simctl get_app_container "$SIM_UDID" "$bundle_id" group.chat.simplex.app)" "$(xcrun simctl get_app_container "$SIM_UDID" "$bundle_id" data)" -name "IMG_*.$1" 2> /dev/null | wc -l
|
||||
}
|
||||
|
||||
sent_file() {
|
||||
local deadline=$((SECONDS + 30))
|
||||
until [ "$(sent_files "$1")" -gt "$2" ]; do
|
||||
[ "$SECONDS" -lt "$deadline" ] || return 1
|
||||
sleep 2
|
||||
done
|
||||
}
|
||||
|
||||
sent_size() {
|
||||
local file
|
||||
file=$(find "$(xcrun simctl get_app_container "$SIM_UDID" "$bundle_id" group.chat.simplex.app)" "$(xcrun simctl get_app_container "$SIM_UDID" "$bundle_id" data)" -name "IMG_*.$1" -exec ls -t {} + 2> /dev/null | sed -n 1p)
|
||||
sips -g pixelWidth -g pixelHeight "$file" | awk '/pixelWidth/ {w = $2} /pixelHeight/ {h = $2} END {print w "x" h}'
|
||||
}
|
||||
|
||||
disable_file_encryption() {
|
||||
xcrun simctl terminate "$SIM_UDID" "$bundle_id" 2> /dev/null || true
|
||||
xcrun simctl spawn "$SIM_UDID" defaults write "$(xcrun simctl get_app_container "$SIM_UDID" "$bundle_id" group.chat.simplex.app)/Library/Preferences/group.chat.simplex.app" privacyEncryptLocalFiles -bool false
|
||||
"$ui" relaunch
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "error: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
create_group() {
|
||||
"$ui" tap Compose
|
||||
"$ui" tap "Create group" 30
|
||||
"$ui" tap-value "Enter group name…" 30
|
||||
"$ui" type "image notes"
|
||||
"$ui" shot new-group
|
||||
tap_element Button "Create group" || fail "no \"Create group\" button"
|
||||
"$ui" tap Skip 10 || true
|
||||
"$ui" shot group-created
|
||||
}
|
||||
|
||||
app_resized_images() {
|
||||
grep -cE "resizeImageTo(Str|Data)Size final" "$UI_OUT/app.log" || true
|
||||
}
|
||||
|
||||
send_image() {
|
||||
local ext=$1 photo_x=$2 size=$3 sent resized
|
||||
sent=$(sent_files "$ext")
|
||||
resized=$(app_resized_images)
|
||||
tap_element Button "Attachements" || fail "no attachment button"
|
||||
"$ui" tap "Choose from library"
|
||||
sleep 3
|
||||
"$ui" shot "picker-$ext"
|
||||
"$ui" tap-xy "$photo_x" "$photo_y"
|
||||
"$ui" shot "picker-selected-$ext"
|
||||
"$ui" tap-xy "$picker_done_x" "$picker_done_y"
|
||||
sleep 3
|
||||
"$ui" shot "preview-$ext"
|
||||
"$ui" find multiply || fail "$ext: no image preview in the compose area"
|
||||
tap_element Button "[^\"]*arrow[^\"]*" || fail "no send button"
|
||||
sent_file "$ext" "$sent" || fail "$ext: no .$ext file sent"
|
||||
"$ui" shot "sent-$ext"
|
||||
[ "$(sent_size "$ext")" = "$size" ] || fail "$ext: the sent image is $(sent_size "$ext"), not $size"
|
||||
[ "$(app_resized_images)" -eq "$resized" ] || fail "$ext: the app resized the image in place of the core"
|
||||
echo "$ext: the picked image was previewed and sent as a $size .$ext file"
|
||||
}
|
||||
|
||||
xcrun simctl install "$SIM_UDID" "$app"
|
||||
xcrun simctl launch "$SIM_UDID" "$bundle_id"
|
||||
|
||||
@@ -129,3 +212,13 @@ products=$(dirname "$app")
|
||||
if [[ "$products" == */Build/Products/Debug-iphonesimulator ]]; then
|
||||
"$(dirname "$0")/snapshots.sh" "${products%/Build/Products/Debug-iphonesimulator}" "$UI_OUT/snapshots"
|
||||
fi
|
||||
|
||||
if [ "$cases" = images ]; then
|
||||
"$ui" tap "Don’t Allow" 10 || true
|
||||
disable_file_encryption
|
||||
xcrun simctl addmedia "$SIM_UDID" "$fixtures/test.jpg" "$fixtures/logo-large-rgba.png"
|
||||
create_group
|
||||
send_image jpg "$test_card_x" 1280x720
|
||||
send_image png "$logo_x" 1630x466
|
||||
echo "smoke test passed: images"
|
||||
fi
|
||||
@@ -39,8 +39,8 @@ scripts/desktop/prepare-openssl-windows.sh
|
||||
openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-3.0.15 | sed 's#/\([a-zA-Z]\)#\1:#' | sed 's#/#\\#g')
|
||||
rm -rf $BUILD_DIR 2>/dev/null || true
|
||||
# Existence of these directories produces build error: cabal's bug
|
||||
# (simplexmq is removed because cabal cannot delete its read-only git submodule pack files - blst, libbbs - on Windows)
|
||||
rm -rf dist-newstyle/src/direct-sq* dist-newstyle/src/simplexmq* 2>/dev/null || true
|
||||
# (simplexmq, hs-jpeg-turbo and JuicyPixels-stbir are removed because cabal cannot delete their read-only git submodule pack files - blst, libbbs, mozjpeg, stb - on Windows)
|
||||
rm -rf dist-newstyle/src/direct-sq* dist-newstyle/src/simplexmq* dist-newstyle/src/hs-jpeg-t* dist-newstyle/src/JuicyPixe* 2>/dev/null || true
|
||||
rm cabal.project.local 2>/dev/null || true
|
||||
echo "ignore-project: False" >> cabal.project.local
|
||||
echo "package direct-sqlcipher" >> cabal.project.local
|
||||
|
||||
@@ -9,4 +9,6 @@
|
||||
"https://github.com/simplex-chat/zip.git"."2eff156c3aac389e35d38bf10a52733d7061640a" = "052vahd5d4lxnazjrb6l60i261aycn2js7jhzafyb72n15ns4r6p";
|
||||
"https://github.com/yesodweb/wai.git"."ec5e017d896a78e787a5acea62b37a4e677dec2e" = "1ckcpmpjfy9jiqrb52q20lj7ln4hmq9v2jk6kpkf3m68c1m9c2bx";
|
||||
"https://github.com/simplex-chat/wai.git"."63667cb577d09c967ddcd945e42c324872dfeb7d" = "1nh4j3fnxbpkdhz1sfgpfhb7j0371apbmx1kv7aqa2zrdxc8gmzb";
|
||||
"https://github.com/simplex-chat/hs-jpeg-turbo.git"."d0a4d954038ee5bcb5c4dc9372a6fb0b05fed77c" = "0b6gl3m05hdaq8ph1nd9b9807k7cdlmwrxvj0rhxv4lp1rkm57rg";
|
||||
"https://github.com/simplex-chat/JuicyPixels-stbir.git"."e0090e14f6348a33b53c11b377cb69df766b02b4" = "0wj8i9wylpas6i7bbyw1vl11vxxy4qz3vx3z9cplzy1nfp914h0g";
|
||||
}
|
||||
@@ -100,6 +100,7 @@ checkout_git() {
|
||||
git -C "$git_dir" reset --hard
|
||||
git -C "$git_dir" clean -dfx
|
||||
git -C "$git_dir" checkout "$tag"
|
||||
git -C "$git_dir" submodule update --init --recursive
|
||||
}
|
||||
|
||||
check_apk() {
|
||||
|
||||
Reference in new issue
Block a user