@ramonclaudio/create-vexpo 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -15
- package/dist/index.js +44 -14
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +14 -1
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +3 -3
- package/dist/templates/default/.maestro/auth.yaml +229 -0
- package/dist/templates/default/.maestro/launch.yaml +5 -5
- package/dist/templates/default/.maestro/tour.yaml +294 -0
- package/dist/templates/default/.maestro/zz-delete-restore.yaml +174 -0
- package/dist/templates/default/AGENTS.md +3 -2
- package/dist/templates/default/DESIGN.md +41 -41
- package/dist/templates/default/README.md +38 -41
- package/dist/templates/default/SETUP.md +34 -19
- package/dist/templates/default/_easignore +0 -1
- package/dist/templates/default/_env.example +15 -10
- package/dist/templates/default/app.config.ts +5 -5
- package/dist/templates/default/convex/pushTokens.ts +1 -26
- package/dist/templates/default/convex/rateLimit.ts +1 -21
- package/dist/templates/default/convex/users.ts +1 -49
- package/dist/templates/default/convex/validators.ts +0 -10
- package/dist/templates/default/package.json +1 -1
- package/dist/templates/default/scripts/README.md +24 -8
- package/dist/templates/default/scripts/clean.ts +3 -3
- package/dist/templates/default/scripts/gen-update-cert.mjs +3 -1
- package/dist/templates/default/src/app/(app)/_layout.tsx +15 -1
- package/dist/templates/default/src/app/(app)/auth/forgot-password.tsx +3 -0
- package/dist/templates/default/src/app/(app)/auth/reset-password.tsx +3 -0
- package/dist/templates/default/src/app/(app)/auth/sign-up.tsx +3 -1
- package/dist/templates/default/src/app/(app)/privacy.tsx +3 -2
- package/dist/templates/default/src/app/(app)/restore-account.tsx +2 -2
- package/dist/templates/default/src/app/(app)/sessions.tsx +15 -5
- package/dist/templates/default/src/components/ui/convex-error.tsx +0 -7
- package/dist/templates/default/src/constants/ui.ts +0 -11
- package/dist/templates/default/src/lib/dev-menu.ts +11 -2
- package/dist/templates/default/src/lib/preferences.ts +9 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@ramonclaudio/create-vexpo)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Scaffold a new [vexpo](https://github.com/ramonclaudio/vexpo) project: Expo SDK 56
|
|
6
|
+
Scaffold a new [vexpo](https://github.com/ramonclaudio/vexpo) project: an Expo SDK 56 iOS app with Convex, Better Auth, and Resend wired in for backend, auth, and email. Push, OTA updates, and App Store submission all run through EAS.
|
|
7
|
+
|
|
8
|
+
This is the opinionated stack I reach for on every new app, Expo and Convex and Better Auth sitting on top of EAS, and I wanted anyone to be able to start from it without a day of wiring. The CLI walks you through creating a Convex account or linking one you already have, so you go from empty folder to a running app without leaving the terminal.
|
|
7
9
|
|
|
8
10
|
## Usage
|
|
9
11
|
|
|
@@ -20,31 +22,39 @@ cd my-app
|
|
|
20
22
|
|
|
21
23
|
npx vexpo lite # 60 seconds: Convex + Better Auth, simulator-ready
|
|
22
24
|
npx vexpo lite --new # same + Convex signup walkthrough for first-time users
|
|
23
|
-
npx vexpo full # full provisioning: TestFlight-ready
|
|
24
|
-
npx vexpo full --new # same + Apple
|
|
25
|
+
npx vexpo full # full provisioning: TestFlight-ready
|
|
26
|
+
npx vexpo full --new # same + Apple, Convex, Expo, and Resend signup walkthrough
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
`npx vexpo lite` is the dev-mode shortcut. No Apple Developer account, no domain, no EAS, no Resend. Boots in the iOS Simulator in
|
|
29
|
+
`npx vexpo lite` is the dev-mode shortcut. No Apple Developer account, no domain, no EAS, no Resend. Boots in the iOS Simulator in about 60 seconds. Add `--new` if you don't have a Convex account yet.
|
|
30
|
+
|
|
31
|
+
`npx vexpo full` validates and provisions everything in order: Convex, Better Auth, Resend, Apple Sign In, EAS, and a rebrand. About 30 minutes hands-on plus Apple-side wait times. It prints the `eas build` command at the end for you to run when ready.
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
## Pre-reqs
|
|
34
|
+
|
|
35
|
+
- macOS with Xcode, to build and run the app in the iOS Simulator.
|
|
36
|
+
- Bun, or Node 20+.
|
|
37
|
+
- An Apple Developer membership, only when you ship to TestFlight or the App Store. Not needed for local dev with `npx vexpo lite`.
|
|
30
38
|
|
|
31
39
|
## Options
|
|
32
40
|
|
|
33
|
-
| Flag | Behavior
|
|
34
|
-
| --------------- |
|
|
35
|
-
| `[directory]` | Project directory name (positional). Defaults to `my-vexpo-app` with `-y
|
|
36
|
-
| `--no-install` | Skip
|
|
37
|
-
| `--no-git` | Skip `git init` after install.
|
|
38
|
-
| `--no-setup` | Skip the
|
|
39
|
-
| `-y, --yes` | Accept defaults, skip prompts.
|
|
40
|
-
| `-v, --version` | Print version, exit.
|
|
41
|
+
| Flag | Behavior |
|
|
42
|
+
| --------------- | -------------------------------------------------------------------------- |
|
|
43
|
+
| `[directory]` | Project directory name (positional). Defaults to `my-vexpo-app` with `-y`. |
|
|
44
|
+
| `--no-install` | Skip installing dependencies after copying the template. |
|
|
45
|
+
| `--no-git` | Skip `git init` after install. |
|
|
46
|
+
| `--no-setup` | Skip the printed next-steps block after install. |
|
|
47
|
+
| `-y, --yes` | Accept defaults, skip prompts. |
|
|
48
|
+
| `-v, --version` | Print version, exit. |
|
|
41
49
|
|
|
42
50
|
## What gets scaffolded
|
|
43
51
|
|
|
44
|
-
The CLI copies `templates/default
|
|
52
|
+
The CLI copies `templates/default/`, restores the dotfiles npm strips from tarballs (`.gitignore`, `.env.example`, `.npmrc`, others), and rewrites `package.json` for the new project. It installs with the package manager it detects from `npm_config_user_agent` (`npm`, `bun`, `pnpm`, or `yarn`, defaulting to `npm`). Then it initializes a git repo with `feat: initial commit`.
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
No lockfile ships in the tarball. The first install resolves the template's ranges fresh, including the latest in-range `vexpo` CLI, and the generated lockfile lands in the initial commit. The `vexpo` CLI installs as a devDependency, so `npx vexpo <subcommand>` resolves to the local pinned version.
|
|
47
55
|
|
|
48
56
|
## Repo
|
|
49
57
|
|
|
50
58
|
[github.com/ramonclaudio/vexpo](https://github.com/ramonclaudio/vexpo)
|
|
59
|
+
|
|
60
|
+
Development happens in the monorepo. See [CONTRIBUTING.md](https://github.com/ramonclaudio/vexpo/blob/main/CONTRIBUTING.md) on GitHub.
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import prompts from 'prompts';
|
|
|
11
11
|
|
|
12
12
|
// package.json
|
|
13
13
|
var package_default = {
|
|
14
|
-
version: "0.1.
|
|
14
|
+
version: "0.1.4"};
|
|
15
15
|
|
|
16
16
|
// src/index.ts
|
|
17
17
|
var here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -19,7 +19,7 @@ var TEMPLATE_DIR = join(here, "templates", "default");
|
|
|
19
19
|
async function main() {
|
|
20
20
|
const program = new Command().name("create-vexpo").description(
|
|
21
21
|
"Scaffold a new vexpo project. Expo SDK 56 + Convex + Better Auth + Resend, wired for iOS."
|
|
22
|
-
).argument("[directory]", "project directory name").option("--no-install", "skip installing dependencies").option("--no-git", "skip git init").option("--no-setup", "skip the
|
|
22
|
+
).argument("[directory]", "project directory name").option("--no-install", "skip installing dependencies").option("--no-git", "skip git init").option("--no-setup", "skip the printed next-steps block after install").option("-y, --yes", "accept defaults, skip prompts").version(package_default.version, "-v, --version").parse();
|
|
23
23
|
const flags = program.opts();
|
|
24
24
|
const argDir = program.args[0];
|
|
25
25
|
intro();
|
|
@@ -43,30 +43,50 @@ Target ${target} already exists. Pick a different name.`));
|
|
|
43
43
|
copySpin.fail("Template copy failed");
|
|
44
44
|
throw err;
|
|
45
45
|
}
|
|
46
|
+
let depsReady = !flags.install;
|
|
46
47
|
if (flags.install) {
|
|
47
48
|
const installSpin = ora(`Installing dependencies with ${kleur.cyan(pm)}`).start();
|
|
48
49
|
try {
|
|
49
|
-
await execa(pm, ["install"], { cwd: target,
|
|
50
|
+
await execa(pm, ["install"], { cwd: target, stdout: "ignore" });
|
|
50
51
|
installSpin.succeed(`Installed with ${pm}`);
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
depsReady = true;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
installSpin.fail(`Install failed. Run ${kleur.cyan(`${pm} install`)} manually.`);
|
|
55
|
+
const stderr = installFailureStderr(err);
|
|
56
|
+
if (stderr) console.error(kleur.gray(tail(stderr)));
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
if (flags.git) {
|
|
56
60
|
const gitSpin = ora("Initializing git").start();
|
|
57
61
|
try {
|
|
58
62
|
await execa("git", ["init", "--initial-branch=main"], { cwd: target, stdio: "ignore" });
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (depsReady) {
|
|
64
|
+
await execa("git", ["add", "-A"], { cwd: target, stdio: "ignore" });
|
|
65
|
+
const identity = await execa("git", ["config", "user.email"], {
|
|
66
|
+
cwd: target,
|
|
67
|
+
reject: false
|
|
68
|
+
});
|
|
69
|
+
if (identity.exitCode !== 0 || !identity.stdout.trim()) {
|
|
70
|
+
gitSpin.warn("Git repo initialized, commit skipped (no git identity)");
|
|
71
|
+
console.error(
|
|
72
|
+
kleur.gray(" Set git config user.name and user.email, then commit yourself.")
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
await execa("git", ["commit", "-m", "feat: initial commit", "--no-gpg-sign"], {
|
|
76
|
+
cwd: target,
|
|
77
|
+
stdio: "ignore"
|
|
78
|
+
});
|
|
79
|
+
gitSpin.succeed("Git repo initialized");
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
gitSpin.warn("Git repo initialized, commit skipped (install failed)");
|
|
83
|
+
console.error(kleur.gray(` Commit yourself after ${pm} install lands.`));
|
|
84
|
+
}
|
|
65
85
|
} catch {
|
|
66
86
|
gitSpin.warn("Git init skipped");
|
|
67
87
|
}
|
|
68
88
|
}
|
|
69
|
-
if (flags.setup) nextSteps(target,
|
|
89
|
+
if (flags.setup) nextSteps(target, pm, depsReady);
|
|
70
90
|
}
|
|
71
91
|
var NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
72
92
|
var NAME_HINT = "lowercase letters, numbers, dashes; must start alphanumeric";
|
|
@@ -157,12 +177,22 @@ function intro() {
|
|
|
157
177
|
console.log();
|
|
158
178
|
console.log(kleur.bold().cyan("create-vexpo") + kleur.gray(` v${package_default.version}`));
|
|
159
179
|
}
|
|
160
|
-
function
|
|
180
|
+
function installFailureStderr(err) {
|
|
181
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
182
|
+
const stderr = err.stderr;
|
|
183
|
+
if (typeof stderr === "string") return stderr.trim();
|
|
184
|
+
}
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
function tail(text, n) {
|
|
188
|
+
return text.split("\n").slice(-20).join("\n");
|
|
189
|
+
}
|
|
190
|
+
function nextSteps(target, pm, depsReady) {
|
|
161
191
|
const cdPath = relative(process.cwd(), target) || ".";
|
|
162
192
|
console.log();
|
|
163
193
|
console.log(kleur.bold("Next steps:"));
|
|
164
194
|
console.log(kleur.gray(" cd ") + kleur.cyan(cdPath));
|
|
165
|
-
if (!
|
|
195
|
+
if (!depsReady) console.log(kleur.gray(` ${pm} install`));
|
|
166
196
|
console.log(
|
|
167
197
|
kleur.gray(
|
|
168
198
|
` npx vexpo lite ${kleur.dim("# dev mode: Convex + Better Auth, 60s to simulator")}`
|
|
@@ -11,6 +11,9 @@ name: E2E tests
|
|
|
11
11
|
# `MAESTRO_APP_ID` resolves to the project's bundle id from the EAS development
|
|
12
12
|
# environment (populated by `vexpo full` or `eas env:create`). The flow
|
|
13
13
|
# yaml at .maestro/launch.yaml reads it as `appId: ${MAESTRO_APP_ID}`.
|
|
14
|
+
#
|
|
15
|
+
# The auth flow (.maestro/auth.yaml) signs a fresh account up against the
|
|
16
|
+
# dev Convex deployment, so `test_creds` mints a unique email per run.
|
|
14
17
|
|
|
15
18
|
# Auto-run on PRs is OFF by default to conserve EAS build credits (each run
|
|
16
19
|
# builds an iOS Simulator binary). Trigger manually, or restore the
|
|
@@ -30,13 +33,23 @@ jobs:
|
|
|
30
33
|
platform: ios
|
|
31
34
|
profile: development:simulator
|
|
32
35
|
|
|
36
|
+
test_creds:
|
|
37
|
+
name: Mint unique test credentials
|
|
38
|
+
outputs:
|
|
39
|
+
email: ${{ steps.creds.outputs.email }}
|
|
40
|
+
steps:
|
|
41
|
+
- id: creds
|
|
42
|
+
run: set-output email "maestro-e2e-$(date +%s)@example.com"
|
|
43
|
+
|
|
33
44
|
maestro:
|
|
34
45
|
name: Run Maestro flows
|
|
35
|
-
needs: [build_ios_simulator]
|
|
46
|
+
needs: [build_ios_simulator, test_creds]
|
|
36
47
|
type: maestro
|
|
37
48
|
environment: development
|
|
38
49
|
env:
|
|
39
50
|
MAESTRO_APP_ID: ${{ env.EXPO_PUBLIC_APP_BUNDLE_ID }}
|
|
51
|
+
MAESTRO_TEST_EMAIL: ${{ needs.test_creds.outputs.email }}
|
|
52
|
+
MAESTRO_TEST_PASSWORD: maestro-e2e-password
|
|
40
53
|
params:
|
|
41
54
|
build_id: ${{ needs.build_ios_simulator.outputs.build_id }}
|
|
42
55
|
flow_path: [".maestro"]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
name: Rotate Apple Sign In JWT
|
|
2
2
|
|
|
3
|
-
# Apple caps client_secret JWTs at 180 days. We re-sign
|
|
4
|
-
# we always have plenty of headroom and never get caught
|
|
5
|
-
# token. Replaces the GitHub Actions equivalent. runs on EAS infrastructure
|
|
3
|
+
# Apple caps client_secret JWTs at 180 days. We re-sign quarterly (the 1st of
|
|
4
|
+
# Jan, Apr, Jul, Oct) so we always have plenty of headroom and never get caught
|
|
5
|
+
# with an expired token. Replaces the GitHub Actions equivalent. runs on EAS infrastructure
|
|
6
6
|
# and reads secrets from EAS env vars (production, secret visibility) rather
|
|
7
7
|
# than GitHub repo secrets.
|
|
8
8
|
#
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Auth journey: sign up (auto-verify lite path) -> land authed -> sign out -> sign back in.
|
|
2
|
+
#
|
|
3
|
+
# This deployment has REQUIRE_EMAIL_VERIFICATION unset, so `getEnabledProviders`
|
|
4
|
+
# returns emailFeatures: false. Sign-up creates a verified account and Better
|
|
5
|
+
# Auth's autoSignIn returns a session in the same call, so there is no OTP step:
|
|
6
|
+
# the user lands signed in. A fresh signed-in user trips the first-launch gate
|
|
7
|
+
# in (app)/_layout.tsx and gets redirected to /welcome; this flow completes that
|
|
8
|
+
# onboarding before asserting the tabs.
|
|
9
|
+
#
|
|
10
|
+
# Same @expo/ui SwiftUI conventions as launch.yaml: `testID` maps to the native
|
|
11
|
+
# accessibilityIdentifier and surfaces to Maestro as `resource-id`, so id-based
|
|
12
|
+
# asserts/taps resolve. Driving notes carry over:
|
|
13
|
+
# - ProminentButton submits hold content in the center, so tap them by `id`.
|
|
14
|
+
# - Segmented Picker rows put their label text in the middle, so tap the
|
|
15
|
+
# sign-in/sign-up toggle by its label text (`tapOn: "Sign up"`), not by id.
|
|
16
|
+
# - The sign-out row is a full-width Button with its label in the center, so
|
|
17
|
+
# tap it by `id`.
|
|
18
|
+
# - waitForAnimationToEnd between navigations; assert-then-tap for tab bars.
|
|
19
|
+
# - Use extendedWaitUntil with a generous timeout after sign-up/sign-in: those
|
|
20
|
+
# hit the live Convex deployment over the network.
|
|
21
|
+
#
|
|
22
|
+
# Env vars:
|
|
23
|
+
# MAESTRO_APP_ID bundle id (EAS injects from EXPO_PUBLIC_APP_BUNDLE_ID)
|
|
24
|
+
# MAESTRO_TEST_EMAIL unique email per run (a fresh account is created)
|
|
25
|
+
# MAESTRO_TEST_PASSWORD password, at least 10 chars (signUpSchema)
|
|
26
|
+
#
|
|
27
|
+
# Local re-runs: Better Auth keeps the session in the keychain, which
|
|
28
|
+
# clearState does NOT wipe. Reset it first or the relaunch comes up signed in:
|
|
29
|
+
# xcrun simctl keychain booted reset
|
|
30
|
+
#
|
|
31
|
+
# Run locally (needs a JDK on PATH):
|
|
32
|
+
# MAESTRO_APP_ID=$(grep '^EXPO_PUBLIC_APP_BUNDLE_ID=' .env.local | cut -d= -f2) \
|
|
33
|
+
# MAESTRO_TEST_EMAIL="e2e+$(date +%s)@example.com" \
|
|
34
|
+
# MAESTRO_TEST_PASSWORD="maestro-test-pw" \
|
|
35
|
+
# maestro test .maestro/auth.yaml
|
|
36
|
+
# Run on EAS: via `.eas/workflows/e2e-tests.yml` (workflow_dispatch only).
|
|
37
|
+
appId: ${MAESTRO_APP_ID}
|
|
38
|
+
---
|
|
39
|
+
- launchApp:
|
|
40
|
+
clearState: true
|
|
41
|
+
- waitForAnimationToEnd:
|
|
42
|
+
timeout: 15000
|
|
43
|
+
|
|
44
|
+
# Dev-client builds: clearState also wipes the dev client's last-opened-bundle
|
|
45
|
+
# memory, so the launcher shows the server picker instead of the app. Tap the
|
|
46
|
+
# running Metro server when the picker appears. Release builds skip this block.
|
|
47
|
+
- runFlow:
|
|
48
|
+
when:
|
|
49
|
+
visible: "DEVELOPMENT SERVERS"
|
|
50
|
+
commands:
|
|
51
|
+
- tapOn:
|
|
52
|
+
text: "http://.*:8081"
|
|
53
|
+
- waitForAnimationToEnd:
|
|
54
|
+
timeout: 15000
|
|
55
|
+
|
|
56
|
+
# Dev-client builds also show a one-time "developer menu" intro sheet after
|
|
57
|
+
# clearState. Drag it down to dismiss. Release builds skip this block too.
|
|
58
|
+
- runFlow:
|
|
59
|
+
when:
|
|
60
|
+
visible: "This is the developer menu.*"
|
|
61
|
+
commands:
|
|
62
|
+
- swipe:
|
|
63
|
+
start: 50%, 55%
|
|
64
|
+
end: 50%, 95%
|
|
65
|
+
- waitForAnimationToEnd:
|
|
66
|
+
timeout: 5000
|
|
67
|
+
|
|
68
|
+
# The intro sheet can appear late while Metro is still bundling, so check a
|
|
69
|
+
# second time after the bundle settles.
|
|
70
|
+
- waitForAnimationToEnd:
|
|
71
|
+
timeout: 10000
|
|
72
|
+
- runFlow:
|
|
73
|
+
when:
|
|
74
|
+
visible: "This is the developer menu.*"
|
|
75
|
+
commands:
|
|
76
|
+
- swipe:
|
|
77
|
+
start: 50%, 55%
|
|
78
|
+
end: 50%, 95%
|
|
79
|
+
- waitForAnimationToEnd:
|
|
80
|
+
timeout: 5000
|
|
81
|
+
|
|
82
|
+
# Signed out: the auth guard mounts the `auth` group, whose stack starts at
|
|
83
|
+
# sign-in. Wait for the screen root before touching anything.
|
|
84
|
+
- extendedWaitUntil:
|
|
85
|
+
visible:
|
|
86
|
+
id: "sign-in-screen"
|
|
87
|
+
timeout: 20000
|
|
88
|
+
# Inner SwiftUI Text testIDs do not surface to Maestro (Host roots and real
|
|
89
|
+
# controls do), so static copy is asserted by its text.
|
|
90
|
+
- assertVisible: "Enter your credentials.*"
|
|
91
|
+
|
|
92
|
+
# Cross to sign-up via the segmented toggle. Tap the segment by label text, not
|
|
93
|
+
# id (the Picker row holds its label in the middle).
|
|
94
|
+
- tapOn: "Sign up"
|
|
95
|
+
- waitForAnimationToEnd:
|
|
96
|
+
timeout: 5000
|
|
97
|
+
- extendedWaitUntil:
|
|
98
|
+
visible:
|
|
99
|
+
id: "sign-up-screen"
|
|
100
|
+
timeout: 15000
|
|
101
|
+
- assertVisible: "Create your account"
|
|
102
|
+
|
|
103
|
+
# Fill name, email, password. Username is optional and skipped. Tapping the
|
|
104
|
+
# field by id focuses it, then `inputText` types into the focused field.
|
|
105
|
+
# The keyboard covers the lower fields once it is up. Both auth screens use
|
|
106
|
+
# scrollDismissesKeyboard("interactively"), so a downward drag dismisses the
|
|
107
|
+
# keyboard between fields (hideKeyboard is flaky on iOS, don't use it).
|
|
108
|
+
- tapOn:
|
|
109
|
+
id: "sign-up-name"
|
|
110
|
+
- inputText: "Maestro E2E"
|
|
111
|
+
- swipe:
|
|
112
|
+
start: 50%, 35%
|
|
113
|
+
end: 50%, 85%
|
|
114
|
+
- scrollUntilVisible:
|
|
115
|
+
element:
|
|
116
|
+
id: "sign-up-email"
|
|
117
|
+
direction: DOWN
|
|
118
|
+
- tapOn:
|
|
119
|
+
id: "sign-up-email"
|
|
120
|
+
- inputText: ${MAESTRO_TEST_EMAIL}
|
|
121
|
+
- swipe:
|
|
122
|
+
start: 50%, 35%
|
|
123
|
+
end: 50%, 85%
|
|
124
|
+
- scrollUntilVisible:
|
|
125
|
+
element:
|
|
126
|
+
id: "sign-up-password"
|
|
127
|
+
direction: DOWN
|
|
128
|
+
# iOS Automatic Strong Password hijacks SecureFields with newPassword content
|
|
129
|
+
# type in the simulator and swallows typed input. The eye toggle swaps to a
|
|
130
|
+
# plain TextField (and auto-focuses it), which types fine.
|
|
131
|
+
- tapOn:
|
|
132
|
+
id: "sign-up-password-visibility"
|
|
133
|
+
- waitForAnimationToEnd:
|
|
134
|
+
timeout: 3000
|
|
135
|
+
- tapOn:
|
|
136
|
+
id: "sign-up-password"
|
|
137
|
+
- inputText: ${MAESTRO_TEST_PASSWORD}
|
|
138
|
+
- swipe:
|
|
139
|
+
start: 50%, 35%
|
|
140
|
+
end: 50%, 85%
|
|
141
|
+
|
|
142
|
+
# Submit. ProminentButton center holds content, so tap by id.
|
|
143
|
+
- scrollUntilVisible:
|
|
144
|
+
element:
|
|
145
|
+
id: "sign-up-submit"
|
|
146
|
+
direction: DOWN
|
|
147
|
+
- tapOn:
|
|
148
|
+
id: "sign-up-submit"
|
|
149
|
+
|
|
150
|
+
# Auto-verify lite path: account is created verified and the session returns in
|
|
151
|
+
# the same call, so the app signs in. A fresh authed user is redirected to the
|
|
152
|
+
# first-launch welcome onboarding. Generous timeout: this is a live network call.
|
|
153
|
+
- extendedWaitUntil:
|
|
154
|
+
visible:
|
|
155
|
+
id: "welcome-screen"
|
|
156
|
+
timeout: 30000
|
|
157
|
+
|
|
158
|
+
# Complete onboarding. Three steps; the button is "Next" until the last step,
|
|
159
|
+
# then "Get Started" (both share id welcome-continue). Skip jumps straight to
|
|
160
|
+
# the app. Tap Skip by label text to dismiss the gate in one move.
|
|
161
|
+
- assertVisible: "Skip"
|
|
162
|
+
- tapOn: "Skip"
|
|
163
|
+
- waitForAnimationToEnd:
|
|
164
|
+
timeout: 5000
|
|
165
|
+
|
|
166
|
+
# Authenticated: the tabs root mounts on home.
|
|
167
|
+
- extendedWaitUntil:
|
|
168
|
+
visible:
|
|
169
|
+
id: "home-screen"
|
|
170
|
+
timeout: 20000
|
|
171
|
+
|
|
172
|
+
# Sign out via Settings. Native tab taps can be flaky, so assert-then-tap.
|
|
173
|
+
- assertVisible: "Settings"
|
|
174
|
+
- tapOn: "Settings"
|
|
175
|
+
- waitForAnimationToEnd:
|
|
176
|
+
timeout: 5000
|
|
177
|
+
- extendedWaitUntil:
|
|
178
|
+
visible:
|
|
179
|
+
id: "settings-screen"
|
|
180
|
+
timeout: 15000
|
|
181
|
+
|
|
182
|
+
# The sign-out row is a capsule row (label left, Spacer center), so tap by text.
|
|
183
|
+
- tapOn: "Sign out"
|
|
184
|
+
- waitForAnimationToEnd:
|
|
185
|
+
timeout: 3000
|
|
186
|
+
# Confirm in the native ConfirmationDialog. The action label is "Sign Out"
|
|
187
|
+
# (capital O), distinct from the row's "Sign out".
|
|
188
|
+
- assertVisible: "Sign Out"
|
|
189
|
+
- tapOn: "Sign Out"
|
|
190
|
+
|
|
191
|
+
# Back to the unauthenticated entry.
|
|
192
|
+
- extendedWaitUntil:
|
|
193
|
+
visible:
|
|
194
|
+
id: "sign-in-screen"
|
|
195
|
+
timeout: 20000
|
|
196
|
+
|
|
197
|
+
# Sign back in with the same credentials. Method toggle defaults to Email.
|
|
198
|
+
- tapOn:
|
|
199
|
+
id: "sign-in-email"
|
|
200
|
+
- inputText: ${MAESTRO_TEST_EMAIL}
|
|
201
|
+
- swipe:
|
|
202
|
+
start: 50%, 35%
|
|
203
|
+
end: 50%, 85%
|
|
204
|
+
# Same strong-password autofill dodge as sign-up: reveal, which auto-focuses.
|
|
205
|
+
- tapOn:
|
|
206
|
+
id: "sign-in-email-password-visibility"
|
|
207
|
+
- waitForAnimationToEnd:
|
|
208
|
+
timeout: 3000
|
|
209
|
+
- tapOn:
|
|
210
|
+
id: "sign-in-email-password"
|
|
211
|
+
- inputText: ${MAESTRO_TEST_PASSWORD}
|
|
212
|
+
- swipe:
|
|
213
|
+
start: 50%, 35%
|
|
214
|
+
end: 50%, 85%
|
|
215
|
+
- scrollUntilVisible:
|
|
216
|
+
element:
|
|
217
|
+
id: "sign-in-submit"
|
|
218
|
+
direction: DOWN
|
|
219
|
+
- tapOn:
|
|
220
|
+
id: "sign-in-submit"
|
|
221
|
+
|
|
222
|
+
# Authenticated again. Onboarding was already marked seen, so this lands on the
|
|
223
|
+
# tabs root directly. Generous timeout: live network call.
|
|
224
|
+
- extendedWaitUntil:
|
|
225
|
+
visible:
|
|
226
|
+
id: "home-screen"
|
|
227
|
+
timeout: 30000
|
|
228
|
+
|
|
229
|
+
- takeScreenshot: auth-final
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
# not by `id`, or the tap lands on empty space. Native tab-bar taps can be flaky;
|
|
13
13
|
# assert-then-tap or retry.
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
# `appId` reads ${MAESTRO_APP_ID}. EAS injects it from EXPO_PUBLIC_APP_BUNDLE_ID
|
|
16
|
+
# (see .eas/workflows/e2e-tests.yml), so the same flow runs against any fork's
|
|
17
|
+
# bundle id without edits. Locally, pass it from .env.local (needs a JDK on PATH):
|
|
18
18
|
#
|
|
19
|
-
# Run locally:
|
|
20
|
-
# Run on EAS:
|
|
19
|
+
# Run locally: MAESTRO_APP_ID=$(grep '^EXPO_PUBLIC_APP_BUNDLE_ID=' .env.local | cut -d= -f2) maestro test .maestro/launch.yaml
|
|
20
|
+
# Run on EAS: via `.eas/workflows/e2e-tests.yml` (workflow_dispatch only).
|
|
21
21
|
appId: ${MAESTRO_APP_ID}
|
|
22
22
|
---
|
|
23
23
|
- launchApp:
|