Skip to content

Commit

Permalink
e2e test for background error telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
fungairino committed Jun 27, 2024
1 parent 6b39117 commit 89fbd0e
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 30 deletions.
160 changes: 134 additions & 26 deletions end-to-end-tests/tests/telemetry/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,54 +26,68 @@ async function waitForBackgroundPageRequest(
return offscreenPage?.waitForRequest(errorServiceEndpoint);
}

async function getSentErrors(
extensionId: string,
context: BrowserContext,
errorServiceEndpoint: "https://browser-intake-datadoghq.com/api/v2/*",
) {
// TODO: due to Datadog SDK implementation, it will take ~30 seconds for the
// request to be sent. We should figure out a way to induce the request to be sent sooner.
// See this datadog support request: https://help.datadoghq.com/hc/en-us/requests/1754158
const request = await waitForBackgroundPageRequest(
context,
extensionId,
errorServiceEndpoint,
);

return request
?.postData()
?.split("\n")
.map((log) => JSON.parse(log));
}

test.use({
additionalRequiredEnvVariables: [
"DATADOG_CLIENT_TOKEN",
"DEV_EVENT_TELEMETRY",
],
});

test("can report application error to telemetry service", async ({
test("can report extension console error to telemetry service", async ({
page,
context,
extensionId,
}) => {
const errorServiceEndpoint = "https://browser-intake-datadoghq.com/api/v2/*";

await context.route(
"https://app.pixiebrix.com/api/extensions/",
async (route) => {
await route.fulfill({
status: 200,
// Returning a bad response to trigger an error
body: JSON.stringify([{}]),
});
},
);
await test.step("Mock the extensions endpoint to return a bad response, and mock errorService calls", async () => {
await context.route(
"https://app.pixiebrix.com/api/extensions/",
async (route) => {
await route.fulfill({
status: 200,
body: JSON.stringify([{}]),
});
},
);

await context.route(errorServiceEndpoint, async (route) =>
route.fulfill({
status: 202,
}),
);
await context.route(errorServiceEndpoint, async (route) =>
route.fulfill({
status: 202,
}),
);
});

await page.goto(getBaseExtensionConsoleUrl(extensionId));
await expect(page.getByText("Something went wrong.")).toBeVisible();

// TODO: due to Datadog SDK implementation, it will take ~30 seconds for the
// request to be sent. We should figure out a way to induce the request to be sent sooner.
const request = await waitForBackgroundPageRequest(
context,
const sentErrors = await getSentErrors(
extensionId,
context,
errorServiceEndpoint,
);

const errorLogsJson = request
?.postData()
?.split("\n")
.map((log) => JSON.parse(log));

expect(errorLogsJson).toContainEqual(
expect(sentErrors).toContainEqual(
expect.objectContaining({
service: "pixiebrix-browser-extension",
manifestVersion: 3,
Expand All @@ -82,6 +96,100 @@ test("can report application error to telemetry service", async ({
message: expect.any(String),
kind: expect.any(String),
}),
// Stack and message are duplicated
stack: expect.any(String),
message: expect.any(String),
connectionType: expect.any(String),
date: expect.any(Number),
extensionVersion: expect.any(String),
name: expect.any(String),
origin: "logger",
pageName: "options",
referrer: "",
runtimeId: extensionId,
session_id: expect.any(String),
status: "error",
url: `chrome-extension://${extensionId}/options.html#/`,
usr: {
email: "[email protected]",
id: "3f7ac0b4-5029-442c-b537-5de9f1dfdfd9",
organizationId: "47f616c5-81e3-4edb-ba44-ed5dd4a78c08",
},
view: {
referrer: "",
url: `chrome-extension://${extensionId}/offscreen.html`,
},
}),
);
});

test("can report a service worker error to telemetry service", async ({
page,
context,
extensionId,
}) => {
const errorServiceEndpoint = "https://browser-intake-datadoghq.com/api/v2/*";

await test.step("Mock the registry endpoint to return a bad response, and mock errorService calls", async () => {
await context.route(
"https://app.pixiebrix.com/api/registry/bricks/",
async (route) => {
await route.fulfill({
status: 500,
body: "I'm not json!",
});
},
);

await context.route(errorServiceEndpoint, async (route) =>
route.fulfill({
status: 202,
}),
);
});

await page.goto(getBaseExtensionConsoleUrl(extensionId));
await expect(page.getByText("An error occurred")).toBeVisible();

const sentErrors = await getSentErrors(
extensionId,
context,
errorServiceEndpoint,
);

expect(sentErrors).toContainEqual(
expect.objectContaining({
code: "ERR_BAD_RESPONSE",
code_version: expect.any(String),
connectionType: "4g",
date: expect.any(Number),
error: expect.objectContaining({
kind: "AxiosError",
message: expect.any(String),
stack: expect.any(String),
}),
extensionVersion: expect.any(String),
manifestVersion: 3,
message: expect.any(String),
name: "AxiosError",
origin: "logger",
pageName: "background",
referrer: "",
runtimeId: extensionId,
service: "pixiebrix-browser-extension",
session_id: expect.any(String),
stack: expect.any(String),
status: "error",
url: "https://app.pixiebrix.com/api/registry/bricks/",
usr: {
email: "[email protected]",
id: "3f7ac0b4-5029-442c-b537-5de9f1dfdfd9",
organizationId: "47f616c5-81e3-4edb-ba44-ed5dd4a78c08",
},
view: {
referrer: "",
url: `chrome-extension://${extensionId}/offscreen.html`,
},
}),
);
});
6 changes: 2 additions & 4 deletions src/registry/packageRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,13 @@ function latestVersion(
* Replace the local database with the packages from the registry.
*
* Memoized to avoid multiple network requests across tabs.
* Consecutive calls are not an issue since the first call is unauthenticated and the
* second is after the user authenticates, because the extension reloads on linking
*/
export const syncPackages = memoizeUntilSettled(async () => {
// The endpoint doesn't return the updated_at timestamp. So use the current local time as our timestamp.
const timestamp = new Date();

// XXX: we currently don't have to worry about consecutive calls where the first call is unauthenticated and the
// second is after the user authenticates, because the extension reloads on linking
const client = await getApiClient();
// In the future, use the paginated endpoint?
const { data } = await client.get<RegistryPackage[]>("/api/registry/bricks/");

const packages = data.map((x) => ({
Expand Down

0 comments on commit 89fbd0e

Please sign in to comment.