Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial revision for e2e tests #12

Merged
merged 10 commits into from
Jul 25, 2024
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SERVER_PORT=
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.env

coverage/
dist/
node_modules/
test-results/
test-results.json
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ We'd love to accept your patches and contributions to this project. Bellow, you
- [Building the SDK](#building-the-sdk)
- [Tests](#tests)
- [Unit Tests](#unit-tests)
- [E2E Tests](#e2e-tests)
- [Code Standards](#code-standards)
- [Submitting contributions](#submitting-contributions)
- [Commit Messages](#commit-messages)
Expand Down Expand Up @@ -94,6 +95,24 @@ To run the unit tests, use the following command:
bun run test
```

### E2E Tests

By default, the application is set to serve a web browser in the port `8080` for Playwright. If this port is already being used on your local machine, make sure you change the port in your `.env`.`SERVER_PORT`.
To run the end-to-end tests, make sure you have installed the browsers supported by playwright by running this command:

```bash
npx playwright install
```

Please refer to [Playwright Documentation](https://playwright.dev/docs/browsers) for details.

Then, also make sure you have the latest bundled files before running it. Use the following commands:

```bash
bun run build
bun run test:e2e
```

## Code Standards

We follow the coding standards set by Biome. Ensure your code follows these guidelines before submitting a pull request. You can run the formatter with the following command:
Expand Down Expand Up @@ -125,6 +144,10 @@ We do conventional commits, so it will fail on checker with capital case after c
The SDK uses following configuration files:
- `tsconfig.json`: TypeScript configuration.
- `tsup.config.ts`: Configuration for the TSUP bundler.
- `playwright.config.ts`: Configuration for E2E tests runner

We also have the following variables as part of the `.env` file:
- `PLAYWRIGHT_PORT`: Port used to run the local web browser to run Playwright E2E tests.

## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ reportEvent(config, event)
400:
```json
{
"status": 204,
"status": 400,
"statusText": "No Content",
"body": {
"errCode": "bad_request",
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[test]
coverage = true
root = "./test"
95 changes: 95 additions & 0 deletions e2e/auctions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { expect, test } from "@playwright/test";
import { apis, baseURL } from "../src/constants/apis.constant";
import { playwrightConstants } from "./config";

test.describe("Create Auction via Topsort SDK", () => {
test("should create an auction successfully", async ({ page }) => {
const mockAPIResponse = {
results: [
{
resultType: "listings",
winners: [],
error: false,
},
{
resultType: "banners",
winners: [],
error: false,
},
],
};

await page.route(`${baseURL}/${apis.auctions}`, async (route) => {
await route.fulfill({ json: mockAPIResponse });
});

await page.goto(playwrightConstants.host);
const result = await page.evaluate(() => {
const config = {
apiKey: "rando-api-key",
};

const auctionDetails = {
auctions: [
{
type: "listings",
slots: 3,
category: { id: "cat123" },
geoTargeting: { location: "US" },
},
{
type: "banners",
slots: 1,
device: "desktop",
slotId: "slot123",
category: { ids: ["cat1", "cat2"] },
geoTargeting: { location: "UK" },
},
],
};
if (typeof window.sdk.createAuction === "undefined") {
throw new Error("Global function `createAuction` is not available.");
}

return window.sdk.createAuction(config, auctionDetails);
});

expect(result).toEqual(mockAPIResponse);
});

test("should fail to call with missing apiKey", async ({ page }) => {
const expectedError = { status: 401, statusText: "API Key is required.", body: {} };
await page.goto(playwrightConstants.host);
const result = await page.evaluate(() => {
const config = {
apiKey: null,
};

const auctionDetails = {
auctions: [
{
type: "listings",
slots: 3,
category: { id: "cat123" },
geoTargeting: { location: "US" },
},
{
type: "banners",
slots: 1,
device: "desktop",
slotId: "slot123",
category: { ids: ["cat1", "cat2"] },
geoTargeting: { location: "UK" },
},
],
};
if (typeof window.sdk.createAuction === "undefined") {
throw new Error("Global function `createAuction` is not available.");
}

return window.sdk.createAuction(config, auctionDetails);
});

expect(result).toEqual(expectedError);
});
});
3 changes: 3 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const playwrightConstants = {
host: `http://localhost:${process.env.SERVER_PORT || 8080}/index.html`,
};
77 changes: 77 additions & 0 deletions e2e/events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expect, test } from "@playwright/test";
import { apis, baseURL } from "../src/constants/apis.constant";
import { playwrightConstants } from "./config";

test.describe("Report Events via Topsort SDK", () => {
test("should report an successfully", async ({ page }) => {
const mockAPIResponse = {
ok: true,
};

await page.route(`${baseURL}/${apis.events}`, async (route) => {
await route.fulfill({ json: mockAPIResponse });
});

await page.goto(playwrightConstants.host);
const result = await page.evaluate(() => {
const config = {
apiKey: "rando-api-key",
};

const event = {
impressions: [
{
resolvedBidId:
"ChAGaP5D2ex-UKEEBCOHwvDjEhABkF4FDAx0S5mMD2cOG0w9GhABkEnL2CB6qKIoqeItVgA_InsKd2h0dHBzOi8vd3d3LndlYmEuYmUvZnIvcHJvbW8uaHRtbD91dG1fc291cmNlPW15c2hvcGkmdXRtX21lZGl1bT1iYW5uZXJfMTI4MHg0MDAmdXRtX2NvbnRlbnQ9ZGlzcGxheSZ1dG1fY2FtcGFpZ249c29sZGVuEAU",
id: "1720706109.713344-53B92988-7A49-4679-B18E-465943B46149",
occurredAt: "2024-07-11T13:55:09Z",
opaqueUserId: "38e0a5ff-9f8a-4e80-8969-e5e3f01348e8",
placement: {
path: "/categories/sports",
},
},
],
};

if (typeof window.sdk.reportEvent === "undefined") {
throw new Error("Global function `reportEvent` is not available.");
}

return window.sdk.reportEvent(config, event);
});

expect(result).toEqual(mockAPIResponse);
});

test("should fail to call with missing apiKey", async ({ page }) => {
const expectedError = { status: 401, statusText: "API Key is required.", body: {} };
await page.goto(playwrightConstants.host);
const result = await page.evaluate(() => {
const config = {
apiKey: null,
};

const event = {
impressions: [
{
resolvedBidId:
"ChAGaP5D2ex-UKEEBCOHwvDjEhABkF4FDAx0S5mMD2cOG0w9GhABkEnL2CB6qKIoqeItVgA_InsKd2h0dHBzOi8vd3d3LndlYmEuYmUvZnIvcHJvbW8uaHRtbD91dG1fc291cmNlPW15c2hvcGkmdXRtX21lZGl1bT1iYW5uZXJfMTI4MHg0MDAmdXRtX2NvbnRlbnQ9ZGlzcGxheSZ1dG1fY2FtcGFpZ249c29sZGVuEAU",
id: "1720706109.713344-53B92988-7A49-4679-B18E-465943B46149",
occurredAt: "2024-07-11T13:55:09Z",
opaqueUserId: "38e0a5ff-9f8a-4e80-8969-e5e3f01348e8",
placement: {
path: "/categories/sports",
},
},
],
};
if (typeof window.sdk.reportEvent === "undefined") {
throw new Error("Global function `reportEvent` is not available.");
}

return window.sdk.reportEvent(config, event);
});

expect(result).toEqual(expectedError);
});
});
30 changes: 30 additions & 0 deletions e2e/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Topsort SDK Test Application</title>
<script src="./index.global.js"></script>
</head>
<body>
<h1>Test Topsort.js Integration</h1>
<script>
window.sdk = {
createAuction: async (config, auctionDetails) => {
const result = await Topsort.createAuction(
config,
auctionDetails
).catch((error) => error);
return result;
},
reportEvent: async (config, eventDetails) => {
const result = await Topsort.reportEvent(
config,
eventDetails
).catch((error) => error);
return result;
},
};
</script>
</body>
</html>
22 changes: 22 additions & 0 deletions e2e/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { file } from "bun";

const PORT = process.env.SERVER_PORT || 8080;

Bun.serve({
fetch(req) {
const url = new URL(req.url);
const pathname = url.pathname === "/" ? "/index.html" : url.pathname;
const filePath = `./dist${pathname}`;

try {
if (filePath.endsWith(".ico")) {
return new Response("", { status: 204 });
}

return new Response(file(filePath));
} catch (e) {
return new Response("Not Found", { status: 404 });
}
},
port: PORT,
});
6 changes: 6 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
interface Window {
sdk: {
createAuction: (a, b) => unknown;
reportEvent: (a, b) => unknown;
};
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@
],
"scripts": {
"build": "tsup",
"test:e2e": "playwright test",
"format": "biome check",
"format:fix": "biome check --write",
"prepare": "lefthook install"
"prepare": "lefthook install",
"serve:e2e": "bun run ./e2e/server.ts"
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@playwright/test": "^1.45.2",
"@types/bun": "1.1.6",
"lefthook": "1.7.2",
"msw": "2.3.1",
Expand Down
31 changes: 31 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: "./e2e",
timeout: 30000,
retries: 0,
reporter: [["list"], ["json", { outputFile: "test-results.json" }]],
use: {
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
],
webServer: {
command: "bun run serve:e2e",
reuseExistingServer: !process.env.CI,
stdout: "ignore",
stderr: "pipe",
},
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
"allowJs": true,
"types": ["node"]
},
"include": ["src/*.ts", "src/**/*.ts"],
"include": ["src/*.ts", "src/**/*.ts", "e2e/server.ts"],
"exclude": ["node_modules"]
}
4 changes: 3 additions & 1 deletion tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
format: ["cjs", "esm", "iife"],
dts: true,
clean: true,
minify: true,
esbuildOptions(options) {
options.keepNames = true;
options.globalName = "Topsort";
},
onSuccess: "cp -r ./e2e/public/* dist",
});