Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
38c0624
removed unecessary changes
Dhruwang Jan 13, 2025
fdc65c7
fix: naming stuff
pandeymangg Jan 13, 2025
1b16cb6
Merge branch 'main' of https://github.com/formbricks/formbricks into …
Dhruwang Jan 15, 2025
218a4e4
Merge branch 'main' into playwright-cloud
Dhruwang Jan 15, 2025
6e0e4c7
update pnpm lock
mattinannt Jan 15, 2025
6e14559
fix az auth
Dhruwang Jan 15, 2025
6b8e6ef
Merge branch 'main' of https://github.com/formbricks/formbricks into …
Dhruwang Jan 15, 2025
12ed2ab
Merge branch 'playwright-cloud' of https://github.com/Dhruwang/formbr…
Dhruwang Jan 15, 2025
2395999
fix condif
Dhruwang Jan 15, 2025
01149ad
Merge branch 'main' into playwright-cloud
Dhruwang Jan 16, 2025
d1f72f9
fix auth
Dhruwang Jan 16, 2025
15026a8
Merge branch 'playwright-cloud' of https://github.com/Dhruwang/formbr…
Dhruwang Jan 16, 2025
1ae89f8
fix az auth
Dhruwang Jan 16, 2025
35f6398
updated workflow
Dhruwang Jan 16, 2025
b03911e
fix workflow
Dhruwang Jan 16, 2025
a1e27a2
removes filter
Dhruwang Jan 16, 2025
77cfc9d
fix pr.yml
Dhruwang Jan 16, 2025
9715b5a
added conditional check
Dhruwang Jan 17, 2025
fd00eec
fix yml file
Dhruwang Jan 17, 2025
14cf972
fix condition
Dhruwang Jan 17, 2025
f8e96e7
fix: secret usage
Dhruwang Jan 17, 2025
a5b101a
fix secrets passing
Dhruwang Jan 17, 2025
0675779
Merge branch 'main' into playwright-cloud
Dhruwang Jan 17, 2025
86aa805
fix: inherting secrets
Dhruwang Jan 17, 2025
2a21b52
Merge branch 'main' into playwright-cloud
Dhruwang Jan 17, 2025
9789203
Merge branch 'main' into playwright-cloud
Dhruwang Jan 20, 2025
af869bb
Merge branch 'main' into playwright-cloud
pandeymangg Jan 22, 2025
454d27d
Merge branch 'main' into playwright-cloud
Dhruwang Jan 22, 2025
aa0fb27
Merge branch 'main' of https://github.com/formbricks/formbricks into …
Dhruwang Jan 23, 2025
15009e9
Merge branch 'playwright-cloud' of https://github.com/formbricks/form…
Dhruwang Jan 23, 2025
d1cd362
Merge branch 'main' into playwright-cloud
Dhruwang Jan 23, 2025
e7212b6
fix: permission
Dhruwang Jan 23, 2025
5351be3
Merge branch 'playwright-cloud' of https://github.com/Dhruwang/formbr…
Dhruwang Jan 23, 2025
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
46 changes: 42 additions & 4 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
name: E2E Tests

on:
workflow_call:
secrets:
AZURE_CLIENT_ID:
required: false
AZURE_TENANT_ID:
required: false
AZURE_SUBSCRIPTION_ID:
required: false
PLAYWRIGHT_SERVICE_URL:
required: false
# Add other secrets if necessary
workflow_dispatch:

env:
TELEMETRY_DISABLED: 1

permissions:
id-token: write
contents: read
actions: read
checks: write
env:
TELEMETRY_DISABLED: 1
checks: write

jobs:
build:
name: Run E2E Tests
Expand Down Expand Up @@ -88,7 +102,31 @@ jobs:
- name: Install Playwright
run: pnpm exec playwright install --with-deps

- name: Run E2E Tests
- name: Set Azure Secret Variables
run: |
if [[ -n "${{ secrets.AZURE_CLIENT_ID }}" && -n "${{ secrets.AZURE_TENANT_ID }}" && -n "${{ secrets.AZURE_SUBSCRIPTION_ID }}" ]]; then
echo "AZURE_ENABLED=true" >> $GITHUB_ENV
else
echo "AZURE_ENABLED=false" >> $GITHUB_ENV
fi

- name: Azure login
if: env.AZURE_ENABLED == 'true'
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Run E2E Tests (Azure)
if: env.AZURE_ENABLED == 'true'
env:
PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }}
run: |
pnpm test-e2e:azure

- name: Run E2E Tests (Local)
if: env.AZURE_ENABLED == 'false'
run: |
pnpm test:e2e

Expand Down
26 changes: 1 addition & 25 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ permissions:
pull-requests: read
actions: read
checks: write
id-token: write

on:
pull_request:
Expand All @@ -19,53 +20,28 @@ concurrency:
cancel-in-progress: true

jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
has-files-requiring-all-checks:
- "!(**.md|.github/CODEOWNERS)"

test:
name: Run Unit Tests
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/test.yml
secrets: inherit

lint:
name: Run Linters
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/lint.yml
secrets: inherit

build:
name: Build Formbricks-web
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/build-web.yml
secrets: inherit

docs:
name: Build Docs
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/build-docs.yml
secrets: inherit

e2e-test:
name: Run E2E Tests
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e.yml
secrets: inherit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const MatrixQuestionForm = ({
<div
className="flex items-center"
onKeyDown={(e) => handleKeyDown(e, "row")}
key={`row-${index}-${question.rows.length}`}>
key={`row-${index}`}>
<QuestionFormInput
id={`row-${index}`}
label={""}
Expand Down Expand Up @@ -219,7 +219,7 @@ export const MatrixQuestionForm = ({
<div
className="flex items-center"
onKeyDown={(e) => handleKeyDown(e, "column")}
key={`column-${index}-${question.columns.length}`}>
key={`column-${index}`}>
<QuestionFormInput
id={`column-${index}`}
label={""}
Expand Down
223 changes: 121 additions & 102 deletions apps/web/playwright/js.spec.ts
Original file line number Diff line number Diff line change
@@ -1,125 +1,144 @@
import { expect } from "@playwright/test";
import http from "http";
import { test } from "./lib/fixtures";
import { replaceEnvironmentIdInHtml } from "./utils/helper";

const HTML_TEMPLATE = `<head>
<script type="text/javascript">
!(function () {
var t = document.createElement("script");
(t.type = "text/javascript"), (t.async = !0), (t.src = "http://localhost:3000/js/formbricks.umd.cjs");
var e = document.getElementsByTagName("script")[0];
e.parentNode.insertBefore(t, e),
setTimeout(function () {
formbricks.init({
environmentId: "ENVIRONMENT_ID",
userId: "RANDOM_USER_ID",
apiHost: "http://localhost:3000",
});
}, 500);
})();
</script>
</head>

<body style="background-color: #fff">
<p>This is my sample page using the Formbricks JS javascript widget</p>
</body>
`;

test.describe("JS Package Test", async () => {
let server: http.Server;
let environmentId: string;

test("Tests", async ({ page, users }) => {
await test.step("Admin creates an In-App Survey", async () => {
const user = await users.create();
await user.login();

await page.waitForURL(/\/environments\/[^/]+\/surveys/);

await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).isVisible();
await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).click();

await page.getByRole("button", { name: "Use this template" }).isVisible();
await page.getByRole("button", { name: "Use this template" }).click();

await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/edit/);

await page.getByRole("button", { name: "Settings", exact: true }).click();

await expect(page.locator("#howToSendCardTrigger")).toBeVisible();
await page.locator("#howToSendCardTrigger").click();

await expect(page.locator("#howToSendCardOption-app")).toBeVisible();
await page.locator("#howToSendCardOption-app").click();
test.beforeAll(async () => {
// Create a simple HTTP server
server = http.createServer((_, res) => {
const htmlContent = HTML_TEMPLATE.replace("ENVIRONMENT_ID", environmentId || "");
res.writeHead(200, { "Content-Type": "text/html" });
res.end(htmlContent);
});

await page.locator("#whenToSendCardTrigger").click();
await new Promise<void>((resolve) => {
server.listen(3004, () => resolve());
});
});

await page.getByRole("button", { name: "Add action" }).click();
await page.getByText("New SessionGets fired when a").click();
test.afterAll(async () => {
// Cleanup: close the server
await new Promise((resolve) => server.close(resolve));
});

await page.locator("#recontactOptionsCardTrigger").click();
test("Create, display and validate PMF survey", async ({ page, users }) => {
// Create and login user
const user = await users.create();
await user.login();

await page.locator("label").filter({ hasText: "Keep showing while conditions" }).click();
await page.locator("#recontactDays").check();
await page.waitForURL(/\/environments\/[^/]+\/surveys/);

await page.getByRole("button", { name: "Publish" }).click();
// Extract environmentId early in the test
environmentId =
/\/environments\/([^/]+)\/surveys/.exec(page.url())?.[1] ??
(() => {
throw new Error("Unable to parse environmentId from URL");
})();

environmentId =
/\/environments\/([^/]+)\/surveys/.exec(page.url())?.[1] ??
(() => {
throw new Error("Unable to parse environmentId from URL");
})();
// Create survey from template
await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).isVisible();
await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).click();
await page.getByRole("button", { name: "Use this template" }).isVisible();
await page.getByRole("button", { name: "Use this template" }).click();

await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/summary/);
});
// Configure survey settings
await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/edit/);
await page.getByRole("button", { name: "Settings", exact: true }).click();

await test.step("JS display survey on page and submit response", async () => {
let currentDir = process.cwd();
let htmlFilePath = currentDir + "/packages/js/index.html";

let htmlFile = replaceEnvironmentIdInHtml(htmlFilePath, environmentId);
await page.goto(htmlFile);

// Formbricks In App Sync has happened
const syncApi = await page.waitForResponse(
(response) => {
return response.url().includes("/environment");
},
{
timeout: 120000,
}
);

expect(syncApi.status()).toBe(200);

// Formbricks Modal exists in the DOM
await expect(page.locator("#formbricks-modal-container")).toHaveCount(1);

// Formbricks Modal is visible
await expect(
page.locator("#questionCard-0").getByRole("link", { name: "Powered by Formbricks" })
).toBeVisible();

// Fill the Survey
await page.getByRole("button", { name: "Happy to help!" }).click();
await page.locator("label").filter({ hasText: "Somewhat disappointed" }).click();
await page.locator("#questionCard-1").getByRole("button", { name: "Next" }).click();
await page.locator("label").filter({ hasText: "Founder" }).click();
await page.locator("#questionCard-2").getByRole("button", { name: "Next" }).click();
await page
.locator("#questionCard-3")
.getByLabel("textarea")
.fill("People who believe that PMF is necessary");
await page.locator("#questionCard-3").getByRole("button", { name: "Next" }).click();
await page.locator("#questionCard-4").getByLabel("textarea").fill("Much higher response rates!");
await page.locator("#questionCard-4").getByRole("button", { name: "Next" }).click();
await page.locator("#questionCard-5").getByLabel("textarea").fill("Make this end to end test pass!");
await page.getByRole("button", { name: "Finish" }).click();

// loading spinner -> wait for it to disappear
await page.getByTestId("loading-spinner").waitFor({ state: "hidden" });
await page.waitForLoadState("networkidle");
});
await expect(page.locator("#howToSendCardTrigger")).toBeVisible();
await page.locator("#howToSendCardTrigger").click();
await expect(page.locator("#howToSendCardOption-app")).toBeVisible();
await page.locator("#howToSendCardOption-app").click();

await test.step("Admin validates Displays & Response", async () => {
await page.goto("/");
await page.waitForURL(/\/environments\/[^/]+\/surveys/);
await page.locator("#whenToSendCardTrigger").click();
await page.getByRole("button", { name: "Add action" }).click();
await page.getByText("New SessionGets fired when a").click();

await page.getByRole("link", { name: "product Market Fit (Superhuman)" }).click();
(await page.waitForSelector("text=Responses")).isVisible();
await page.locator("#recontactOptionsCardTrigger").click();
await page.locator("label").filter({ hasText: "Keep showing while conditions" }).click();
await page.locator("#recontactDays").check();

await page.waitForLoadState("networkidle");
await page.waitForTimeout(2000);
await page.getByRole("button", { name: "Publish" }).click();

const impressionsCount = await page.getByRole("button", { name: "Impressions" }).innerText();
expect(impressionsCount).toEqual("Impressions\n\n1");
await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/summary/);

await expect(page.getByRole("link", { name: "Responses (1)" })).toBeVisible();
await expect(page.getByRole("button", { name: "Completed 100%" })).toBeVisible();
// No need for file operations anymore, just use the server
await page.goto("http://localhost:3004");

await expect(page.getByText("1 Responses", { exact: true }).first()).toBeVisible();
await expect(page.getByText("CTR100%")).toBeVisible();
await expect(page.getByText("Somewhat disappointed100%")).toBeVisible();
await expect(page.getByText("Founder100%")).toBeVisible();
await expect(page.getByText("People who believe that PMF").first()).toBeVisible();
await expect(page.getByText("Much higher response rates!").first()).toBeVisible();
await expect(page.getByText("Make this end to end test").first()).toBeVisible();
const syncApi = await page.waitForResponse((response) => response.url().includes("/environment"), {
timeout: 120000,
});
expect(syncApi.status()).toBe(200);

await expect(page.locator("#formbricks-modal-container")).toHaveCount(1);
await expect(
page.locator("#questionCard-0").getByRole("link", { name: "Powered by Formbricks" })
).toBeVisible();

// Fill the survey
await page.getByRole("button", { name: "Happy to help!" }).click();
await page.locator("label").filter({ hasText: "Somewhat disappointed" }).click();
await page.locator("#questionCard-1").getByRole("button", { name: "Next" }).click();
await page.locator("label").filter({ hasText: "Founder" }).click();
await page.locator("#questionCard-2").getByRole("button", { name: "Next" }).click();
await page
.locator("#questionCard-3")
.getByLabel("textarea")
.fill("People who believe that PMF is necessary");
await page.locator("#questionCard-3").getByRole("button", { name: "Next" }).click();
await page.locator("#questionCard-4").getByLabel("textarea").fill("Much higher response rates!");
await page.locator("#questionCard-4").getByRole("button", { name: "Next" }).click();
await page.locator("#questionCard-5").getByLabel("textarea").fill("Make this end to end test pass!");
await page.getByRole("button", { name: "Finish" }).click();

await page.getByTestId("loading-spinner").waitFor({ state: "hidden" });
await page.waitForLoadState("networkidle");

// Validate displays and response
await page.goto("/");
await page.waitForURL(/\/environments\/[^/]+\/surveys/);
await page.getByRole("link", { name: "product Market Fit (Superhuman)" }).click();
await page.waitForSelector("text=Responses");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(2000);

const impressionsCount = await page.getByRole("button", { name: "Impressions" }).innerText();
expect(impressionsCount).toEqual("Impressions\n\n1");

await expect(page.getByRole("link", { name: "Responses (1)" })).toBeVisible();
await expect(page.getByRole("button", { name: "Completed 100%" })).toBeVisible();
await expect(page.getByText("1 Responses", { exact: true }).first()).toBeVisible();
await expect(page.getByText("CTR100%")).toBeVisible();
await expect(page.getByText("Somewhat disappointed100%")).toBeVisible();
await expect(page.getByText("Founder100%")).toBeVisible();
await expect(page.getByText("People who believe that PMF").first()).toBeVisible();
await expect(page.getByText("Much higher response rates!").first()).toBeVisible();
await expect(page.getByText("Make this end to end test").first()).toBeVisible();
});
});
Loading
Loading