Skip to main content

testerpaykit 2.0.0 copy "testerpaykit: ^2.0.0" to clipboard
testerpaykit: ^2.0.0 copied to clipboard

In-app bug reports for TesterPayKit testers — screenshots, recordings and voice notes, crash capture, sessions and engaged minutes.

TesterPayKit — Flutter SDK #

Let real testers report bugs from inside your Flutter app. Testers shake the device or tap the floating button and file a report with the screenshot (annotated if they like), a screen recording or a spoken voice note. The SDK adds device and app state, the screens they went through, and crashes on its own. Reports, sessions and engaged testing minutes land in your TesterPayKit project, where you triage them and pay your testers.

You need a TesterPayKit project. The SDK sends everything to the TesterPayKit API and is tied to a project ID. Accounts are currently invite-only — to request access, write to business@moinsen.dev.

Install #

flutter pub add testerpaykit

Quick start #

Get your project ID from the TesterPayKit web console, or with the tpk CLI: tpk --json project create --name "My App" --bundle-id com.example.myapp.

Initialise the SDK before runApp — fail-safe, so a TesterPayKit problem never takes your app down:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:testerpaykit/testerpaykit.dart';

const _projectId = String.fromEnvironment('TPK_PROJECT_ID');

Future<TesterPayKit?> initTesterPayKit() async {
  if (_projectId.isEmpty) return null;
  try {
    return await TesterPayKit.initialize(
      config: TesterPayKitConfig(
        projectId: _projectId,
        apiBaseUrl: 'https://api.testerpaykit.com',
        enableTesterMode: true,
        debugMode: kDebugMode,
      ),
    );
  } catch (e) {
    debugPrint('TesterPayKit disabled: $e');
    return null;
  }
}

final _navigatorKey = GlobalKey<NavigatorState>();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final tpk = await initTesterPayKit();
  runApp(MyApp(tpk: tpk));
}

class MyApp extends StatelessWidget {
  const MyApp({required this.tpk, super.key});

  final TesterPayKit? tpk;

  @override
  Widget build(BuildContext context) {
    final sdk = tpk;
    return MaterialApp(
      navigatorKey: _navigatorKey,
      // Screen views drive the testers' engaged minutes.
      navigatorObservers: [if (sdk != null) sdk.navigatorObserver],
      builder: (context, child) {
        final content = child ?? const SizedBox.shrink();
        if (sdk == null) return content;
        return TesterPayKitWrapper(
          sdk: sdk,
          navigatorKey: _navigatorKey,
          child: content,
        );
      },
      home: const HomeScreen(),
    );
  }
}

Run it with your project ID:

flutter run --dart-define=TPK_PROJECT_ID=<project-uuid>

Shake the device or tap the floating button to file a test report, then check that it arrived: tpk bug list --bundle-id com.example.myapp.

Unpaid beta testing #

For an unpaid beta, pass showCompensation: false to TesterPayKitWrapper. The real tester controls remain available, while the earnings estimate is hidden in both the menu and session-end screen. This controls presentation only: ensure the project's server-side payout rules and recruitment terms also match the intended unpaid test.

Testers #

The TesterPayKit widget — floating button, shake and three-finger tap, crash prompt, onboarding and every tester screen — only appears for people with a confirmed DE/AT/CH mobile number and your approval. That holds in every build, TestFlight and beta included: everyone else sees nothing from TesterPayKit. Session telemetry keeps running.

Give people a way in from your own UI, e.g. a "Become a tester" entry in your settings:

final tpk = TesterPayKit.instance;

ValueListenableBuilder<TpkAccess>(
  valueListenable: tpk.access,
  builder: (context, access, _) => access == TpkAccess.ready
      ? const SizedBox.shrink()
      : ListTile(
          title: const Text('Become a tester'),
          onTap: () => tpk.openVerification(context),
        ),
);

openVerification(context) asks for the invite code when the build has none, opens the phone confirmation, and says so while your approval is pending. Once access is ready, the button appears. The SDK checks again on every app resume and after any refusal from the server, so a revoked approval hides the widget. refreshAccess() re-checks on demand. The invite code is remembered across launches; for your own debug or CI builds, pass one with testerId: in TesterPayKitConfig.

GoRouter and other Router 2.0 setups #

The navigator observer only sees Navigator 1.0 route pushes. With GoRouter, report screens yourself on every route change — otherwise engaged minutes, and with them your testers' payouts, come out too low:

TesterPayKit.instance.trackScreenView('settings');

AI test runs started from outside #

A coordinator can start an AI test run from the web console ("Start on device") or the terminal (tpk autotest --project <id>). An app built with aiRemoteTrigger: true picks the run up and lets the model drive it — nobody has to tap anything on the device:

TesterPayKitWrapper(
  sdk: sdk,
  aiRemoteTrigger: true, // off by default
  // aiRemotePollSeconds: 15,
  child: child,
)

While the app is in the foreground and no AI run is open, the wrapper asks the API every 15 seconds whether a run waits for this project. A run is handed to one app only. The overlay closes itself 3 seconds after the run ends, and polling resumes. The model gets a screenshot of the app on every step (areas wrapped in TesterSensitive stay masked); the TesterPayKit button and the AI overlay are hidden from it.

Only enable this in builds you want remote-controlled, such as your own debug or test builds. It needs a tester token for the project (the invite code above).

More API #

Call What it does
trackScreenView(name) / trackTap() Manual screen and tap tracking.
submitBugReport(description: …) File a report from code; returns a local id, uploads in the background.
submitFeedback(content: …, rating: …) General feedback.
completeTask(taskId) Mark a test task as done.
checkForUnreportedCrashes(context) Offer to report a crash from the last run (only while access is ready).
access / refreshAccess() / openVerification(context) Who sees the widget; see Testers.
flushAndEndSession() Upload pending data and close the session.

Platform support #

Android and iOS.

Requirements #

  • Dart ^3.11.0
  • Flutter >=3.41.0

License #

Commercial licence, not open source: you may use the SDK unmodified as a dependency of your own apps, commercial ones included, to connect them to the TesterPayKit service. Details in LICENSE.

Support #

business@moinsen.dev · testerpaykit.com