import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; import 'package:build_daemon/client.dart'; import 'package:build_daemon/constants.dart'; import 'package:build_daemon/data/build_status.dart'; import 'package:build_daemon/data/build_target.dart'; import 'package:collection/collection.dart'; // ignore: implementation_imports import 'package:drift/src/web/wasm_setup/types.dart'; import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; import 'package:shelf/shelf_io.dart'; import 'package:shelf_proxy/shelf_proxy.dart'; import 'package:web_wasm/initialization_mode.dart'; import 'package:webdriver/async_io.dart'; import 'package:webdriver/support/async.dart'; class TestAssetServer { final BuildDaemonClient buildRunner; late final HttpServer server; TestAssetServer(this.buildRunner); Future close() async { await server.close(force: true); await buildRunner.close(); } static Future start({ bool debug = false, int? fixedPort, }) async { final packageConfig = await loadPackageConfigUri((await Isolate.packageConfig)!); final ownPackage = packageConfig['web_wasm']!.root; var packageDir = ownPackage.toFilePath(windows: Platform.isWindows); if (packageDir.endsWith('/') || packageDir.endsWith('\\')) { packageDir = packageDir.substring(0, packageDir.length - 1); } final buildRunner = await BuildDaemonClient.connect( packageDir, [ Platform.executable, // dart 'run', 'build_runner', 'daemon', '-d', if (debug) '--define=build_web_compilers:entrypoint=dart2js_args=["-Dsqlite3.wasm.worker.debug=true"]' ], logHandler: (log) => print(log.message), ); buildRunner ..registerBuildTarget(DefaultBuildTarget((b) => b.target = 'web')) ..startBuild(); // Wait for the build to complete, so that the server we return is ready to // go. await buildRunner.buildResults.firstWhere((b) { final buildResult = b.results.firstWhereOrNull((r) => r.target == 'web'); return buildResult != null && buildResult.status != BuildStatus.started; }); final assetServerPortFile = File(p.join(daemonWorkspace(packageDir), '.asset_server_port')); final assetServerPort = int.parse(await assetServerPortFile.readAsString()); final server = TestAssetServer(buildRunner); final proxy = proxyHandler('http://localhost:$assetServerPort/web/'); server.server = await serve( (request) async { final pathSegments = request.url.pathSegments; if (pathSegments.isNotEmpty && pathSegments[0] == 'no-coep') { // Serve stuff under /no-coep like the regular website, but without // adding the security headers. return await proxy(request.change(path: 'no-coep')); } else { final response = await proxy(request); if (!request.url.path.startsWith('/no-coep')) { return response.change(headers: { // Needed for shared array buffers to work 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }); } return response; } }, 'localhost', fixedPort ?? 0, ); return server; } } class DriftWebDriver { final TestAssetServer server; final WebDriver driver; DriftWebDriver(this.server, this.driver); /// Wait for the Dart code on the test page to finish its main method, which /// it signals by creating an element. Future waitReady() async { await waitFor(() => driver.findElement(By.id('ready'))); } Future< ({ Set storages, Set missingFeatures, List existing, })> probeImplementations({bool withWrongWorkerUri = false}) async { final method = withWrongWorkerUri ? 'detectImplementationsWrongUri' : 'detectImplementations'; final rawResult = await driver.executeAsync('$method("", arguments[0])', []); final result = json.decode(rawResult); return ( storages: { for (final entry in result['impls']) WasmStorageImplementation.values.byName(entry) }, missingFeatures: { for (final entry in result['missing']) MissingBrowserFeature.values.byName(entry) }, existing: [ for (final entry in result['existing']) ( WebStorageApi.byName[entry[0] as String]!, entry[1] as String, ), ], ); } Future openDatabase({ WasmStorageImplementation? implementation, bool moveIndexedDbToOpfs = false, }) async { await driver.executeAsync('open(arguments[0], arguments[1])', [ json.encode({ 'implementation': implementation?.name, 'moveToOpfs': moveIndexedDbToOpfs, }), ]); } Future closeDatabase() async { await driver.executeAsync("close('', arguments[0])", []); } Future insertIntoDatabase() async { await driver.executeAsync('insert("", arguments[0])', []); } Future runExclusiveBlock() async { await driver.executeAsync('do_exclusive("", arguments[0])', []); } Future get amountOfRows async { return await driver.executeAsync('get_rows("", arguments[0])', []); } Future get hasTable async { return await driver.executeAsync('has_table("", arguments[0])', []); } Future waitForTableUpdate() async { await driver.executeAsync('wait_for_update("", arguments[0])', []); } Future enableInitialization(InitializationMode mode) async { final result = await driver.executeAsync( 'enable_initialization(arguments[0], arguments[1])', [mode.name], ); if (result != true) { throw 'Could not set initialization mode'; } } Future setSchemaVersion(int version) async { final result = await driver.executeAsync( 'set_schema_version(arguments[0], arguments[1])', [version.toString()], ); if (result != true) { throw 'Could not set schema version'; } } Future deleteDatabase(WebStorageApi storageApi, String name) async { await driver.executeAsync('delete_database(arguments[0], arguments[1])', [ json.encode([storageApi.name, name]), ]); } Future exportDatabase(WebStorageApi storageApi, String name) async { return await driver .executeAsync('export_database(arguments[0], arguments[1])', [ json.encode([storageApi.name, name]), ]); } Future isDart2wasm() async { return await driver.executeAsync('isDart2wasm("", arguments[0])', []); } }