@coinbase/onchaintestkit
Version:
End-to-end testing toolkit for blockchain applications, powered by Playwright
170 lines (169 loc) • 7.02 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PhantomFixturesBuilder = PhantomFixturesBuilder;
const test_1 = require("@playwright/test");
const _1 = require(".");
const LocalNodeManager_1 = require("../../node/LocalNodeManager");
const createTempDir_1 = require("../../utils/createTempDir");
const extensionManager_1 = require("../../utils/extensionManager");
const removeTempDir_1 = require("../../utils/removeTempDir");
let sharedPhantomPage;
// Constants
const PHANTOM_API_URL = "https://api.phantom.app/portal/v1/public-apps**";
const PHANTOM_NODE_PROXY = "node-proxy.phantom.app";
// EXPERIMENTAL: Service Worker network interception (requires PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1)
// See: https://playwright.dev/docs/network#service-worker-network-events
// To enable: set environment variable PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1
async function setupPhantomNetworkInterception(context, localNodePort) {
// Intercept ALL requests, including those from service workers (if experimental flag is enabled)
await context.route("**", async (route) => {
const request = route.request();
// Check if this request originated from a service worker
if (request.serviceWorker()) {
const postData = request.postData();
// Optionally, reroute to local node if it's a node-proxy call
if (request.url().includes(PHANTOM_NODE_PROXY)) {
try {
const localUrl = `http://localhost:${localNodePort}`;
const response = await context.request.post(localUrl, {
data: postData,
headers: { "Content-Type": "application/json" },
});
const responseBody = await response.text();
await route.fulfill({
status: response.status(),
headers: response.headers(),
body: responseBody,
});
return;
}
catch (error) {
console.error("[SW] Error rerouting service worker request:", error);
}
}
// Otherwise, continue as normal
await route.continue();
}
else {
// Not a service worker request, handle as before
await route.continue();
}
});
// Mock Phantom's domain validation API to allow localhost
await context.route(PHANTOM_API_URL, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
data: {
apps: [{ domain: "localhost", verified: true }],
domains: ["localhost"],
},
}),
});
});
console.log("[Phantom Network] Service worker interception enabled (experimental)");
}
function PhantomFixturesBuilder(walletConfig, nodeConfig) {
return test_1.test.extend({
// Add node fixture that will start before any wallet setup
node: nodeConfig
? [
async ({}, use) => {
try {
const node = new LocalNodeManager_1.LocalNodeManager(nodeConfig);
await node.start();
console.log(`Node is ready on port ${node.port}`);
await use(node);
console.log("Node stopping...");
await node.stop();
}
catch (error) {
console.error("Error in node fixture:", error);
throw error;
}
},
{ scope: "test", auto: true },
]
: undefined,
_contextPath: async ({}, use, testInfo) => {
const contextPath = await (0, createTempDir_1.createTempDir)(testInfo.testId);
await use(contextPath);
const error = await (0, removeTempDir_1.removeTempDir)(contextPath);
if (error) {
console.error(error);
}
},
context: async ({ context: currentContext, _contextPath, node }, use) => {
try {
const { phantomPage, phantomContext } = await _1.PhantomWallet.initialize(currentContext, _contextPath, walletConfig);
sharedPhantomPage = phantomPage;
// CRITICAL: Set up Phantom-specific network interception
if (node?.port) {
await setupPhantomNetworkInterception(phantomContext, node.port);
}
await use(phantomContext);
await phantomContext.close();
}
catch (error) {
console.error("Error in context fixture:", error);
throw error;
}
},
phantomPage: async ({ context: _ }, use) => {
await use(sharedPhantomPage);
},
extensionId: async ({ context }, use) => {
try {
const extensionId = await (0, extensionManager_1.getExtensionId)(context, "Phantom");
await use(extensionId);
}
catch (error) {
console.error("Error in extensionId fixture:", error);
throw error;
}
},
phantom: [
async ({ context, extensionId }, use) => {
try {
const phantom = new _1.PhantomWallet(walletConfig, context, sharedPhantomPage, extensionId);
await use(phantom);
}
catch (error) {
console.error("Error in phantom fixture:", error);
throw error;
}
},
{ scope: "test", auto: true },
],
setupWallet: [
async ({ phantom, node }, use) => {
try {
console.log("Setting up Phantom wallet...");
if (walletConfig.walletSetup) {
await walletConfig.walletSetup(phantom, {
localNodePort: node?.port || 8545,
});
}
console.log("Phantom wallet setup complete");
await use(null);
}
catch (error) {
console.error("Error during wallet setup:", error);
throw error;
}
},
{ scope: "test", auto: true },
],
page: async ({ page }, use) => {
try {
await page.goto("/");
await use(page);
}
catch (error) {
console.error("Error in page fixture:", error);
throw error;
}
},
});
}