diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc32bb..c749beb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ prompt. Write them for users. read-only, and when the feed changes one, Run and Paste show you the new commands first. - Two devices refreshing the same cloud source at once no longer make duplicate hosts or "(conflict)" copies. +- About links to the documentation (morphgate.com/docs) and the source code on GitHub. +- Copy buttons (a public key, the recovery key, an invite code, host key details) fall back to + another way of copying when the system refuses the first, and say "Couldn't copy" instead of + doing nothing when neither works. ## 0.2.0 diff --git a/apps/desktop/e2e/README.md b/apps/desktop/e2e/README.md index c9827f1..4a569ee 100644 --- a/apps/desktop/e2e/README.md +++ b/apps/desktop/e2e/README.md @@ -8,6 +8,7 @@ These run the real AppImage, deb and rpm binaries, not a dev build, and drive th | `package.mjs` (deb, rpm) | The binary knows how it was installed, finds the update, and warns that installing asks for an administrator password (installing needs root, so it stops there) | | `teams.mjs` | Two apps side by side (Alice and Bob) with a local reference sync server: both turn on sync; Alice creates an organization and a vault and invites Bob; Bob joins only after the owner's fingerprint matches, and Alice confirms him only after his matches; a host saved in the shared vault reaches Bob, his edit reaches Alice; Bob publishes his SSH key; Alice revokes him (the vault leaves his device) and rotates the vault key; members may create vaults; the activity log tells the story. Then Alice offboards Bob against the local sshd: removal and key rotation, his key found and removed from `authorized_keys`, and the host's shared key replaced (she still logs in) | | `layout.mjs` | Turns sync on from the offer a new vault gets, fills every page (hosts in a group, a key, a snippet, a tunnel, a team with an invite to a long address), and at 860, 1024, 1280 and 1600 px wide checks that nothing on a page overlaps or runs out of its column. Closing an editor returns to the page it was opened from, and closing a page to the session under it; with no session open, a page takes the tab bar's place | +| `monkey.mjs` | Only with `E2E_ONLY=monkey`: each package (AppImage, deb, rpm) takes `MONKEY_STEPS` seeded random clicks, typing, keys, selects and resizes over a vault with hosts where nothing listens; any page error, blank page, unanswered call or Rust panic fails it, with the seed and the last steps in `monkey-/failure.txt` (`MONKEY_SEED` repeats it) | | `demo.mjs` | Only with `E2E_ONLY=demo`: the README's demo. The AppImage's Hosts page, a click on a host and its recorded session (in a demo shell), drawn by `tools/readme-demo/demo-svg.mjs` as `demo/morphgate.svg` (copy it to `assets/demo/morphgate.svg`) | | `mobile.mjs` | The phone layout in a 390 × 844 window: imports a key and saves a host on the phone screens, connects from the host's sheet, interrupts a command with Ctrl from the extra-key row and repeats one with ↑, keeps the session open behind the host list, browses into a folder over SFTP and downloads a file to Downloads, then locks and unlocks on the phone unlock screen | diff --git a/apps/desktop/e2e/appimage.mjs b/apps/desktop/e2e/appimage.mjs index 0f59487..66771d3 100644 --- a/apps/desktop/e2e/appimage.mjs +++ b/apps/desktop/e2e/appimage.mjs @@ -980,6 +980,13 @@ try { assert.match(await openAbout(s), new RegExp(`Version ${version} · AppImage`)); ok(`About: ${version}, AppImage`); + // Its documentation and its source code, in the browser (browser.sh notes what it's given). + await s.clickButton("Documentation ↗"); + await s.clickButton("Source code on GitHub ↗"); + const opened = () => (existsSync(process.env.E2E_BROWSER_LOG) ? readFileSync(process.env.E2E_BROWSER_LOG, "utf8") : ""); + await s.wait(() => (/^https:\/\/morphgate\.com\/docs$/m.test(opened()) && /^https:\/\/github\.com\/Vallic\/MorphGate$/m.test(opened())) || null, "the documentation and the source code opened"); + ok("About opens the documentation and the source code"); + // The third-party notices that come with the app: a crate's, from the generated list. await s.clickButton("Open-source licenses"); await s.wait(() => s.exec(`return document.querySelector(".licenses-list li") ? true : null`), "the license list"); diff --git a/apps/desktop/e2e/browser.sh b/apps/desktop/e2e/browser.sh index 12f752e..f1bb59a 100755 --- a/apps/desktop/e2e/browser.sh +++ b/apps/desktop/e2e/browser.sh @@ -1,4 +1,8 @@ #!/bin/sh -# What the app opens sign-in pages with during e2e (MORPHGATE_BROWSER): follows the redirects -# like a browser, from the identity provider back to the sync server. -exec curl -sSL --max-time 30 -o /dev/null "$1" +# What the app opens web pages with during e2e (MORPHGATE_BROWSER): follows the redirects like a +# browser, from the identity provider back to the sync server. Pages off this computer (the docs, +# GitHub) aren't fetched; every address is noted in E2E_BROWSER_LOG when it's set. +[ -n "${E2E_BROWSER_LOG:-}" ] && echo "$1" >>"$E2E_BROWSER_LOG" +case "$1" in + http://127.0.0.1[:/]* | http://localhost[:/]*) exec curl -sSL --max-time 30 -o /dev/null "$1" ;; +esac diff --git a/apps/desktop/e2e/monkey.mjs b/apps/desktop/e2e/monkey.mjs new file mode 100644 index 0000000..b006eea --- /dev/null +++ b/apps/desktop/e2e/monkey.mjs @@ -0,0 +1,231 @@ +// A monkey test of a packaged build: a vault with hosts, groups, keys and snippets, then a seeded +// random walk through the app (clicks on whatever is there, typing, keys, selects, the window +// resized), checking after every step that the page threw nothing, still renders and still +// answers. A failure names the seed, the step and the last steps taken, with a screenshot. +// +// node monkey.mjs