Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions examples/smoke_app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,55 @@ Expected result:
- the Scenario fails at `scroll_read_only_text`
- the failure reason contains `does not identify a scrollable`
- failure diagnostics are still written under the run directory

## PilotRuntime Replacement Calibration

The `pilot/calibration` Project Scenarios are the live replacement checks for
the `pilot_runtime` path. They run against one debug Target App Package target
and cover `byText`, semantic `byType`, `byKey`, `byWidget`, tap, type, targeted
scroll, untargeted scroll, Screenshot, Widget Tree, Logs, and Project Run hot
restart between Scenarios.

The calibration app routes `package:logging` records through `debugPrint`, so a
capture step should write a non-empty `.log` artifact.

Run the macOS desktop debug calibration from `examples/smoke_app`:

```bash
dart run ../../bin/flutter_pilot.dart test pilot/calibration \
--target lib/pilot_runtime_calibration_app.dart \
--device macos
```

Run the Android debug calibration by replacing the device id with a connected
debug device:

```bash
dart run ../../bin/flutter_pilot.dart test pilot/calibration \
--target lib/pilot_runtime_calibration_app.dart \
--device <android-device-id>
```

Expected Project Run result:

- `01_interact.yaml` passes and writes Screenshot, Widget Tree, and Logs
artifacts.
- Flutter Pilot hot restarts the Target App Package before
`02_after_restart.yaml`.
- `02_after_restart.yaml` sees `Calibration taps: 0`, proving the restart reset
app state while `PilotRuntimeBinding` and capture still work.
- The Project Run writes `project_run_report.json`; each Scenario Run writes
`run_report.json` and `timeline.html`.

Calibration output should record:

- platform: macOS desktop debug or Android debug
- Flutter SDK version
- selected Target Device id
- exact command
- observed Project Run result and artifact paths
- any unsupported capability or platform-specific difference

Web, profile, release, and iOS are not claimed by v1. These examples do not
change Flutter Pilot runtime selection behavior; they only provide a focused
Project Run for live `pilot_runtime` calibration.
182 changes: 182 additions & 0 deletions examples/smoke_app/lib/pilot_runtime_calibration_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:pilot_runtime/pilot_runtime.dart';

final Logger _log = Logger('pilot_runtime_calibration');

/// Start the replacement-calibration app with `pilot_runtime` installed.
///
/// This target is intentionally broader than the focused tap and scroll demos:
/// it exercises Finder constraints, replay actions, capture artifacts, and
/// Project Run hot restart behavior against one real debug Runtime Target.
void main() {
WidgetsFlutterBinding.ensureInitialized();
PilotRuntimeBinding.ensureInitialized();
_configureLogging();
_log.info('Pilot Runtime calibration app started.');
runApp(const PilotRuntimeCalibrationApp());
}

/// Route package logging records through Flutter debug output.
///
/// Flutter Pilot captures `debugPrint` through `PilotRuntimeBinding`, so this
/// keeps the target on the public `logging` API while producing a non-empty
/// runtime Logs artifact during calibration capture.
void _configureLogging() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((LogRecord record) {
debugPrint('${record.level.name} ${record.loggerName}: ${record.message}');
});
}

/// Smoke app used by the live `pilot_runtime` replacement calibration.
class PilotRuntimeCalibrationApp extends StatelessWidget {
const PilotRuntimeCalibrationApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Pilot Runtime Calibration',
theme: ThemeData(colorSchemeSeed: Colors.teal),
home: const PilotRuntimeCalibrationPage(),
);
}
}

/// Single target page that exposes stable Finder and action evidence.
class PilotRuntimeCalibrationPage extends StatefulWidget {
const PilotRuntimeCalibrationPage({super.key});

@override
State<PilotRuntimeCalibrationPage> createState() =>
_PilotRuntimeCalibrationPageState();
}

class _PilotRuntimeCalibrationPageState
extends State<PilotRuntimeCalibrationPage> {
final TextEditingController _emailController = TextEditingController();
int _tapCount = 0;
int _chipTapCount = 0;

@override
void dispose() {
_emailController.dispose();
super.dispose();
}

/// Record a semantic button tap for the calibration Scenario.
void _recordTap() {
setState(() {
_tapCount += 1;
});
_log.info('Calibration button tapped $_tapCount time(s).');
}

/// Record a custom-widget tap for the byKey plus byWidget calibration path.
void _recordChipTap() {
setState(() {
_chipTapCount += 1;
});
_log.info('Calibration chip tapped $_chipTapCount time(s).');
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pilot Runtime Calibration')),
body: ListView(
padding: const EdgeInsets.all(24),
children: <Widget>[
const Text(
'Pilot Runtime Calibration Ready',
key: ValueKey<String>('calibration_ready_text'),
),
const SizedBox(height: 16),
TextField(
key: const ValueKey<String>('calibration_email_field'),
controller: _emailController,
decoration: const InputDecoration(labelText: 'Calibration email'),
),
const SizedBox(height: 16),
FilledButton(
key: const ValueKey<String>('calibration_button'),
onPressed: _recordTap,
child: const Text('Calibration tap'),
),
const SizedBox(height: 12),
Text(
'Calibration taps: $_tapCount',
key: const ValueKey<String>('calibration_tap_count'),
),
const SizedBox(height: 16),
CalibrationChip(
key: const ValueKey<String>('calibration_chip'),
taps: _chipTapCount,
onTap: _recordChipTap,
),
const SizedBox(height: 12),
Text(
'Calibration chip taps: $_chipTapCount',
key: const ValueKey<String>('calibration_chip_count'),
),
const SizedBox(height: 24),
SizedBox(
height: 180,
child: ListView(
key: ValueKey<String>('calibration_target_scrollable'),
padding: EdgeInsets.all(12),
children: <Widget>[
Text('Calibration nested row 0'),
SizedBox(height: 56),
Text('Calibration nested row 1'),
SizedBox(height: 56),
Text('Calibration nested row 2'),
SizedBox(height: 56),
Text('Calibration nested row 3'),
SizedBox(height: 56),
Text('Calibration nested row 4'),
SizedBox(height: 56),
Text('Calibration nested row 5'),
SizedBox(height: 56),
Text('Calibration nested row 6'),
SizedBox(height: 56),
Text('Calibration nested row 7'),
],
),
),
const SizedBox(height: 24),
for (int index = 0; index < 28; index += 1)
Padding(
padding: const EdgeInsets.symmetric(vertical: 14),
child: Text('Calibration page row $index'),
),
],
),
);
}
}

/// Custom tap target used to calibrate exact Dart widget type matching.
class CalibrationChip extends StatelessWidget {
const CalibrationChip({required this.taps, required this.onTap, super.key});

/// Number of taps recorded by the parent page.
final int taps;

/// Callback invoked when this custom widget is tapped.
final VoidCallback onTap;

@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 52,
alignment: Alignment.center,
color: Colors.teal.shade100,
child: Text('Calibration chip target ($taps)'),
),
);
}
}
17 changes: 17 additions & 0 deletions examples/smoke_app/lib/pilot_runtime_scroll_demo.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:pilot_runtime/pilot_runtime.dart';

final Logger _log = Logger('pilot_runtime_scroll_demo');

/// Start the scroll replay demo with the real `pilot_runtime` binding installed.
///
/// This target is intended for manual Flutter Pilot acceptance runs. It exposes
Expand All @@ -9,9 +12,23 @@ import 'package:pilot_runtime/pilot_runtime.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
PilotRuntimeBinding.ensureInitialized();
_configureLogging();
_log.info('Pilot Runtime scroll demo started.');
runApp(const PilotRuntimeScrollDemoApp());
}

/// Route package logging records through Flutter debug output.
///
/// Flutter Pilot captures `debugPrint` through `PilotRuntimeBinding`, so this
/// keeps the smoke app on the public `logging` API while still producing a
/// runtime Logs artifact during Scenario capture.
void _configureLogging() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((LogRecord record) {
debugPrint('${record.level.name} ${record.loggerName}: ${record.message}');
});
}

/// Demo app used to verify scroll replay through `PilotRuntimeAdapter`.
class PilotRuntimeScrollDemoApp extends StatelessWidget {
const PilotRuntimeScrollDemoApp({super.key});
Expand Down
64 changes: 64 additions & 0 deletions examples/smoke_app/pilot/calibration/01_interact.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
scenario:
name: pilot_runtime_calibration_01_interact
description: |
Exercises the live pilot_runtime replacement checks before Project Run hot
restart resets the Target App Package for the next Scenario.

steps:
- label: wait_for_calibration_ready
waitFor:
byText: Pilot Runtime Calibration Ready
timeoutMs: 5000

- label: tap_calibration_button_by_text_and_type
tap:
byText: Calibration tap
byType: button

- label: wait_for_tap_count
waitFor:
byText: 'Calibration taps: 1'
byKey: calibration_tap_count
timeoutMs: 5000

- label: type_calibration_field_by_semantic_type
type:
byType: textField
text: calibrated@example.com

- label: tap_calibration_chip_by_key_and_widget
tap:
byKey: calibration_chip
byWidget: CalibrationChip

- label: wait_for_chip_count
waitFor:
byText: 'Calibration chip taps: 1'
byKey: calibration_chip_count
timeoutMs: 5000

- label: targeted_scroll_calibration_list
scroll:
byKey: calibration_target_scrollable
byType: scrollable
deltaY: -260

- label: wait_for_nested_scroll_result
waitFor:
byText: Calibration nested row 6
timeoutMs: 5000

- label: primary_scroll_calibration_page
scroll:
deltaY: -900

- label: wait_for_primary_scroll_result
waitFor:
byText: Calibration page row 24
timeoutMs: 5000

- label: capture_calibration_artifacts
capture:
screenshot: true
widgetTree: true
logs: true
24 changes: 24 additions & 0 deletions examples/smoke_app/pilot/calibration/02_after_restart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
scenario:
name: pilot_runtime_calibration_02_after_restart
description: |
Verifies the Project Run hot restart boundary by checking that the runtime
binding, Finder resolution, and capture artifacts still work after the
previous Scenario changed app state.

steps:
- label: verify_hot_restart_reset_state
waitFor:
byText: 'Calibration taps: 0'
byKey: calibration_tap_count
timeoutMs: 5000

- label: verify_runtime_binding_after_restart
waitFor:
byText: Pilot Runtime Calibration Ready
timeoutMs: 5000

- label: capture_after_restart_artifacts
capture:
screenshot: true
widgetTree: true
logs: true
10 changes: 10 additions & 0 deletions examples/smoke_app/pilot/pilot_runtime_scroll.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ scenario:
Verifies targeted scroll replay through PilotRuntimeAdapter.

steps:
- label: tt
waitFor:
byText: Scroll demo row 1
timeoutMs: 5000

- label: scroll_target_list
scroll:
byKey: target_scroll_list
Expand All @@ -14,3 +19,8 @@ steps:
waitFor:
byText: Scroll demo row 18
timeoutMs: 5000

- label: capture_logs
capture:
logs: true
widgetTree: false
8 changes: 8 additions & 0 deletions examples/smoke_app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
logging:
dependency: "direct main"
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
Expand Down
Loading
Loading