Skip to content

Commit f86e31b

Browse files
committed
feat: ns compiled verify compares a compiled release with its JavaScript release
Builds both releases of the project, then runs the project's @nativescript/compiler ns-compiled-verify on them: the same steps (the project's verify.json, or a shot after launch) on one iOS Simulator, compared pixel by pixel, failing where a screenshot differs or an app stops running.
1 parent 31ab3e7 commit f86e31b

4 files changed

Lines changed: 147 additions & 1 deletion

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<% if (isJekyll) { %>---
2+
title: ns compiled verify
3+
position: 20
4+
---<% } %>
5+
6+
# ns compiled verify
7+
8+
### Description
9+
10+
Builds the project's JavaScript release and its compiled release (`--compiled`), runs both the same way on one iOS Simulator and compares them: a screenshot of each step, pixel by pixel, and whether either app stops running. Use it before shipping a compiled release.
11+
12+
The steps are the project's `verify.json`, when it has one. Each screen launches the app afresh; coordinates are points on the simulator's screen:
13+
14+
```json
15+
{
16+
"screens": [
17+
{ "name": "home", "steps": [["shot", "start"], ["tap", 200, 400], ["wait", 1], ["type", "Ada"], ["shot", "typed"]] }
18+
]
19+
}
20+
```
21+
22+
Steps: `shot <name>`, `tap <x> <y> [seconds held]`, `taps <x> <y> [count]`, `swipe <x> <y> <toX> <toY>`, `drag <x> <y> <toX> <toY> [seconds]`, `type <text>`, `wait <seconds>`, `openurl <url>`. Without `verify.json`, the app is compared once it settles after launch.
23+
24+
The screenshots and `report.json` are written to `platforms/compiled/verify`. The command fails when a screenshot differs or an app stops running.
25+
26+
<% if(isConsole && (isWindows || isLinux)) { %>WARNING: You can run this command only on macOS systems.<% } %>
27+
28+
### Commands
29+
30+
Usage | Synopsis
31+
---|---
32+
General | `$ ns compiled verify [ios]`
33+
34+
### Prerequisites
35+
36+
* `@nativescript/compiler` as a devDependency of the project.
37+
* Xcode with an iOS Simulator runtime.
38+
39+
<% if(isHtml) { %>
40+
41+
### Related Commands
42+
43+
Command | Description
44+
----------|----------
45+
[build ios](build-ios.html) | Builds the project for iOS, `--compiled` to compile it to native code.
46+
<% } %>

‎lib/bootstrap.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ injector.requireCommand("run|android", "./commands/run");
184184
injector.requireCommand("run|vision", "./commands/run");
185185
injector.requireCommand("run|visionos", "./commands/run");
186186
injector.requireCommand("typings", "./commands/typings");
187+
injector.requireCommand("compiled|verify", "./commands/compiled-verify");
187188

188189
injector.requireCommand("preview", "./commands/preview");
189190

‎lib/commands/compiled-verify.ts‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as path from "path";
2+
import { IChildProcess, IErrors, IFileSystem } from "../common/declarations";
3+
import { ICommand, ICommandParameter } from "../common/definitions/commands";
4+
import { injector } from "../common/yok";
5+
import { IProjectData } from "../definitions/project";
6+
import { CompiledReleaseService } from "../services/compiled-release-service";
7+
8+
/**
9+
* `ns compiled verify [ios]`: builds the JavaScript release and the compiled
10+
* release of the project, runs both the same way on one simulator and compares
11+
* them, a screenshot of each step pixel by pixel and whether either stops
12+
* running. The steps are the project's `verify.json`, when it has one
13+
* (`{ "screens": [{ "name": "home", "steps": [["shot", "start"], ["tap", 200, 400], ["shot", "tapped"]] }] }`);
14+
* without it, a screenshot once the app settles.
15+
*/
16+
export class CompiledVerifyCommand implements ICommand {
17+
public allowedParameters: ICommandParameter[] = [];
18+
19+
constructor(
20+
private $childProcess: IChildProcess,
21+
private $compiledReleaseService: CompiledReleaseService,
22+
private $errors: IErrors,
23+
private $fs: IFileSystem,
24+
private $logger: ILogger,
25+
private $projectData: IProjectData,
26+
) {}
27+
28+
public async execute(args: string[]): Promise<void> {
29+
const platform = (args[0] ?? "ios").toLowerCase();
30+
if (platform !== "ios") {
31+
this.$errors.fail(
32+
"ns compiled verify compares iOS builds; Android is to follow.",
33+
);
34+
}
35+
this.$projectData.initializeProjectData();
36+
const projectDir = this.$projectData.projectDir;
37+
const cli = process.argv[1];
38+
const ns = (...buildArgs: string[]) =>
39+
this.$childProcess.spawnFromEvent(
40+
process.execPath,
41+
[cli, "build", "ios", ...buildArgs],
42+
"close",
43+
{ cwd: projectDir, stdio: "inherit" },
44+
);
45+
46+
this.$logger.info("Building the JavaScript release...");
47+
await ns("--release", "--no-compiled");
48+
this.$logger.info("Building the compiled release...");
49+
await ns("--compiled");
50+
51+
const js = this.findApp(
52+
path.join(this.$projectData.platformsDir, "ios", "build", "Release-iphonesimulator"),
53+
);
54+
const compiled = this.findApp(
55+
path.join(this.$projectData.platformsDir, "compiled", "ios", "DerivedData", "Build", "Products", "Release-iphonesimulator"),
56+
);
57+
const steps = path.join(projectDir, "verify.json");
58+
const out = path.join(this.$projectData.platformsDir, "compiled", "verify");
59+
const verify = this.$compiledReleaseService.getCompilerBin(
60+
this.$projectData,
61+
"ns-compiled-verify",
62+
);
63+
try {
64+
await this.$childProcess.spawnFromEvent(
65+
process.execPath,
66+
[verify, "--js", js, "--compiled", compiled, "--out", out, ...(this.$fs.exists(steps) ? ["--steps", steps] : [])],
67+
"close",
68+
{ cwd: projectDir, stdio: "inherit" },
69+
);
70+
} catch {
71+
this.$errors.fail(
72+
`The compiled release differs from its JavaScript release: see ${out}`,
73+
);
74+
}
75+
}
76+
77+
private findApp(dir: string): string {
78+
const app = this.$fs.exists(dir)
79+
? this.$fs.readDirectory(dir).find((f) => f.endsWith(".app"))
80+
: undefined;
81+
if (!app) {
82+
this.$errors.fail(`No simulator build in ${dir}.`);
83+
}
84+
85+
return path.join(dir, app);
86+
}
87+
}
88+
89+
injector.registerCommand("compiled|verify", CompiledVerifyCommand);

‎lib/services/compiled-release-service.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,20 @@ export class CompiledReleaseService {
142142
}
143143

144144
private getCompilerPath(projectData: IProjectData): string {
145+
return this.getCompilerBin(projectData, "ns-native");
146+
}
147+
148+
/** A command of the project's `@nativescript/compiler` (`ns-native`, `ns-compiled-verify`), as a script for Node. */
149+
public getCompilerBin(projectData: IProjectData, name: string): string {
145150
const packageDir = this.getPackageDir(projectData);
146151
const { bin } = this.$fs.readJson(path.join(packageDir, "package.json"));
152+
if (!bin?.[name]) {
153+
this.$errors.fail(
154+
`The project's ${COMPILER_PACKAGE_NAME} has no ${name}: update it.`,
155+
);
156+
}
147157

148-
return path.join(packageDir, bin["ns-native"]);
158+
return path.join(packageDir, bin[name]);
149159
}
150160

151161
private getPackageDir(projectData: IProjectData): string {

0 commit comments

Comments
 (0)