Add integration tests for petition (#10646)
* add integration tests for petition
This commit is contained in:
Родитель
aced10ec6f
Коммит
c5d309a385
|
@ -145,9 +145,10 @@ jobs:
|
|||
USE_S3: False
|
||||
X_FRAME_OPTIONS: DENY
|
||||
XSS_PROTECTION: True
|
||||
CSP_FONT_SRC: "'self' https://fonts.gstatic.com https://fonts.googleapis.com https://code.cdn.mozilla.net"
|
||||
CSP_SCRIPT_SRC: "'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/gsap.min.js https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/ScrollTrigger.min.js"
|
||||
CSP_STYLE_SRC: "'self' 'unsafe-inline' https://code.cdn.mozilla.net https://fonts.googleapis.com https://platform.twitter.com"
|
||||
CSP_CONNECT_SRC: "*"
|
||||
CSP_FONT_SRC: "'self' https://fonts.gstatic.com https://fonts.googleapis.com https://code.cdn.mozilla.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/fonts/ data:"
|
||||
CSP_SCRIPT_SRC: "'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/gsap.min.js https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/ScrollTrigger.min.js https://*.googletagmanager.com https://*.fundraiseup.com https://mozillafoundation.tfaforms.net 'unsafe-eval'"
|
||||
CSP_STYLE_SRC: "'self' 'unsafe-inline' https://code.cdn.mozilla.net https://fonts.googleapis.com https://platform.twitter.com https://mozillafoundation.tfaforms.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
|
|
|
@ -2,6 +2,7 @@ from factory import SubFactory, Trait
|
|||
from wagtail.models import Page as WagtailPage
|
||||
|
||||
from networkapi.utility.faker.helpers import get_homepage, reseed
|
||||
from networkapi.wagtailpages.donation_modal import DonationModals
|
||||
from networkapi.wagtailpages.models import CampaignIndexPage, CampaignPage
|
||||
|
||||
from .abstract import CMSPageFactory
|
||||
|
@ -21,6 +22,11 @@ class CampaignPageFactory(CMSPageFactory):
|
|||
|
||||
class Params:
|
||||
no_cta = Trait(cta=None)
|
||||
cta_show_all_fields = Trait(
|
||||
cta=SubFactory(
|
||||
PetitionFactory, requires_country_code=True, requires_postal_code=True, comment_requirements="required"
|
||||
)
|
||||
)
|
||||
|
||||
cta = SubFactory(PetitionFactory)
|
||||
|
||||
|
@ -55,7 +61,13 @@ def generate(seed):
|
|||
print("Generating single-page CampaignPage")
|
||||
# Most Campaign Pages on prod use wide content layout,
|
||||
# Setting narrowed_page_content to False to make it easier to test the real use case
|
||||
CampaignPageFactory.create(parent=campaign_index_page, title="single-page", narrowed_page_content=False)
|
||||
CampaignPageFactory.create(
|
||||
parent=campaign_index_page,
|
||||
title="single-page",
|
||||
narrowed_page_content=False,
|
||||
donation_modals=[DonationModals.objects.first()],
|
||||
cta_show_all_fields=True,
|
||||
)
|
||||
|
||||
reseed(seed)
|
||||
|
||||
|
|
|
@ -14,9 +14,10 @@ Please follow the steps below every time after you paste a new version of FormAs
|
|||
- Update the variable names to they match the new FormAssembly field names
|
||||
- Plug in those variables into corresponding form fields in formassembly_body.html
|
||||
|
||||
4. If form field names are changed, update the bottom section of formassembly_override.scss to reflect new field names
|
||||
(Reference to the current fields:
|
||||
Go to https://mozillafoundation.tfaforms.net/forms/builder/5.0.0/9 and click on "OUTLINE" tab)
|
||||
4. If form field names are changed:
|
||||
(Field ref: https://mozillafoundation.tfaforms.net/forms/builder/5.0.0/9 and click on "OUTLINE" tab)
|
||||
- Update the bottom section of formassembly_override.scss to reflect new field names
|
||||
- Update FA_FIELDS and FA_HIDDEN_FIELDS in tests/integration/petition/utility.js to reflect new field names
|
||||
|
||||
5. Wrap country field, postal code field, and comment field in {% if %} tag
|
||||
- for country field: {% if show_country_field %} ... {% endif %}
|
||||
|
|
|
@ -4,6 +4,7 @@ const config = {
|
|||
headless: true,
|
||||
viewport: { width: 1280, height: 720 },
|
||||
ignoreHTTPSErrors: true,
|
||||
permissions: ["clipboard-read"],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
const { test, expect } = require("@playwright/test");
|
||||
const waitForImagesToLoad = require("../../wait-for-images.js");
|
||||
const utility = require("./utility.js");
|
||||
|
||||
test.describe("React form", () => {
|
||||
test("Visibility", async ({ page }) => {
|
||||
await page.goto(utility.generateUrl("en"));
|
||||
await page.locator("body.react-loaded");
|
||||
await waitForImagesToLoad(page);
|
||||
|
||||
// test if the React form is visible
|
||||
const reactForm = page.locator("#petition-form");
|
||||
// wait for the form to be attached to the DOM
|
||||
await reactForm.waitFor({ state: "visible" });
|
||||
expect(await reactForm.count()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("FormAssembly petition form", () => {
|
||||
const TIMESTAMP = Date.now();
|
||||
// locales we support on foundation.mozilla.org
|
||||
let supportedLocales = [
|
||||
"en",
|
||||
"de",
|
||||
"es",
|
||||
"fr",
|
||||
"fy-NL",
|
||||
"nl",
|
||||
"pl",
|
||||
"pt-BR",
|
||||
"sw",
|
||||
];
|
||||
let localeToTest = supportedLocales[0];
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
await page.goto(utility.generateUrl(localeToTest, utility.FA_PAGE_QUERY));
|
||||
await page.locator("body.react-loaded");
|
||||
await waitForImagesToLoad(page);
|
||||
|
||||
// test if the FormAssembly form is visible
|
||||
const wFormContainer = page.locator(".wFormContainer");
|
||||
await wFormContainer.waitFor({ state: "visible" });
|
||||
expect(await wFormContainer.count()).toBe(1);
|
||||
|
||||
// test if there's a submit button
|
||||
const submitButton = wFormContainer.locator(`input[type="submit"]`);
|
||||
expect(await submitButton.count()).toBe(1);
|
||||
|
||||
// test if required fields exist and are empty (not pre-filled)
|
||||
const firstNameInput = wFormContainer.locator(utility.FA_FIELDS.firstName);
|
||||
expect(await firstNameInput.count()).toBe(1);
|
||||
expect(await firstNameInput.inputValue()).toBe("");
|
||||
|
||||
const lastNameInput = wFormContainer.locator(utility.FA_FIELDS.lastName);
|
||||
expect(await lastNameInput.count()).toBe(1);
|
||||
expect(await lastNameInput.inputValue()).toBe("");
|
||||
|
||||
const emailInput = wFormContainer.locator(utility.FA_FIELDS.email);
|
||||
expect(await emailInput.count()).toBe(1);
|
||||
expect(await emailInput.inputValue()).toBe("");
|
||||
|
||||
const privacyInput = wFormContainer.locator(utility.FA_FIELDS.privacy);
|
||||
expect(await privacyInput.count()).toBe(1);
|
||||
expect(await privacyInput.isChecked()).toBe(false);
|
||||
|
||||
// test if hidden fields exist and are indeed hidden
|
||||
const campaignIdInput = wFormContainer.locator(
|
||||
utility.FA_HIDDEN_FIELDS.campaignId
|
||||
);
|
||||
expect(await campaignIdInput.count()).toBe(1);
|
||||
expect(await campaignIdInput).toBeHidden();
|
||||
|
||||
const thankYouUrlInput = wFormContainer.locator(
|
||||
utility.FA_HIDDEN_FIELDS.thankYouUrl
|
||||
);
|
||||
expect(await thankYouUrlInput.count()).toBe(1);
|
||||
expect(await thankYouUrlInput).toBeHidden();
|
||||
expect(await thankYouUrlInput.inputValue()).toContain(
|
||||
utility.THANK_YOU_PAGE_QUERY
|
||||
);
|
||||
|
||||
const sourceUrlInput = wFormContainer.locator(
|
||||
utility.FA_HIDDEN_FIELDS.sourceUrl
|
||||
);
|
||||
expect(await sourceUrlInput.count()).toBe(1);
|
||||
expect(await sourceUrlInput).toBeHidden();
|
||||
expect((await sourceUrlInput.inputValue()).split("?")[0]).toContain(
|
||||
utility.generateUrl(localeToTest)
|
||||
);
|
||||
|
||||
const langInput = wFormContainer.locator(utility.FA_HIDDEN_FIELDS.lang);
|
||||
expect(await langInput.count()).toBe(1);
|
||||
expect(await langInput).toBeHidden();
|
||||
expect(await langInput.inputValue()).toBe(localeToTest);
|
||||
|
||||
const newsletterInput = wFormContainer.locator(
|
||||
utility.FA_HIDDEN_FIELDS.newsletter
|
||||
);
|
||||
expect(await newsletterInput.count()).toBe(1);
|
||||
expect(await newsletterInput).toBeHidden();
|
||||
|
||||
// test if submitting the form without filling out the required fields creates validation errors
|
||||
// wait for submitButton's click event to be attached
|
||||
await submitButton.waitFor({ state: "attached" });
|
||||
await submitButton.click();
|
||||
expect(await page.locator(".errFld").count()).toBe(4);
|
||||
expect(await page.locator(".errMsg").count()).toBe(4);
|
||||
|
||||
// test if filling out the form and submitting it eliminates the validation errors
|
||||
await firstNameInput.fill("Integration");
|
||||
await lastNameInput.fill("Test");
|
||||
await emailInput.fill(`test-${TIMESTAMP}-${localeToTest}@example.com`);
|
||||
await privacyInput.check();
|
||||
|
||||
// Update campaign id to TEST_CAMPAIGN_ID so this test can be submitted to FormAssembly
|
||||
// We can't use locator because the campaign id field is hidden
|
||||
await page.evaluate(
|
||||
({ campaignFieldId, testCampaignId, note }) => {
|
||||
if (document.querySelector(campaignFieldId)) {
|
||||
document.querySelector(campaignFieldId).value = testCampaignId;
|
||||
}
|
||||
|
||||
if (document.querySelector(`textarea[name="tfa_497"]`)) {
|
||||
document.querySelector(`textarea[name="tfa_497"]`).value = note;
|
||||
}
|
||||
},
|
||||
{
|
||||
campaignFieldId: utility.FA_HIDDEN_FIELDS.campaignId,
|
||||
testCampaignId: utility.TEST_CAMPAIGN_ID,
|
||||
note: `${testInfo.title} by integration test`,
|
||||
}
|
||||
);
|
||||
|
||||
// prepare to wait for the form to submit
|
||||
const navigationPromise = page.waitForNavigation();
|
||||
await submitButton.click();
|
||||
await navigationPromise;
|
||||
});
|
||||
|
||||
for (const locale of supportedLocales) {
|
||||
test(`(${locale}) Signing petition`, async ({ page }) => {
|
||||
localeToTest = locale;
|
||||
// Form has been submitted successfully. Page should be redirected to thank you page
|
||||
expect(page.url()).toContain(utility.THANK_YOU_PAGE_QUERY);
|
||||
});
|
||||
|
||||
test(`(${locale}) Signing petition using the same email`, async ({
|
||||
page,
|
||||
}) => {
|
||||
localeToTest = locale;
|
||||
// We turned off a config so that Salesforce errors won't be visible to the user.
|
||||
// This means signing the petition using the same email address should still send users to the thank you page
|
||||
expect(page.url()).toContain(utility.THANK_YOU_PAGE_QUERY);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
const { test, expect } = require("@playwright/test");
|
||||
const waitForImagesToLoad = require("../../wait-for-images.js");
|
||||
const utility = require("./utility.js");
|
||||
|
||||
test.describe("Donation modal", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(utility.generateUrl("en", utility.THANK_YOU_PAGE_QUERY));
|
||||
await page.locator("body.react-loaded");
|
||||
await waitForImagesToLoad(page);
|
||||
|
||||
// test if donation modal is visible
|
||||
let modalContent = page.locator(`.modal-content`);
|
||||
await modalContent.waitFor({ state: "visible" });
|
||||
});
|
||||
|
||||
test("Donation modal can be closed using the 'x' button", async ({
|
||||
page,
|
||||
}) => {
|
||||
// test if donation modal can be closed using the "x" button
|
||||
const closeButton = page.locator(
|
||||
`.modal-content button[data-dismiss="modal"].close`
|
||||
);
|
||||
expect(await closeButton.count()).toBe(1);
|
||||
await closeButton.click();
|
||||
expect(await page.locator(`.modal-content`).isVisible()).toBe(false);
|
||||
});
|
||||
|
||||
test("Donation modal can be closed using the 'No thanks' button", async ({
|
||||
page,
|
||||
}) => {
|
||||
// test if donation modal can be closed using the "No thanks" button
|
||||
const noThanksButton = page.locator(
|
||||
`.modal-content button.text.dismiss[data-dismiss="modal"]`
|
||||
);
|
||||
expect(await noThanksButton.count()).toBe(1);
|
||||
await noThanksButton.click();
|
||||
expect(await page.locator(`.modal-content`).isVisible()).toBe(false);
|
||||
});
|
||||
|
||||
test("Donation modal can trigger FRU widget", async ({ page }) => {
|
||||
// test if FRU iframe pops up after clicking the Yes button
|
||||
const yesDonateButton = page.locator(
|
||||
`.modal-content .tw-btn-primary[href="?form=donate"]`
|
||||
);
|
||||
expect(await yesDonateButton.count()).toBe(1);
|
||||
|
||||
const navigationPromise = page.waitForNavigation();
|
||||
await yesDonateButton.click();
|
||||
await navigationPromise;
|
||||
|
||||
// check if URL contains query parameter "form=donate"
|
||||
expect(page.url()).toContain(`form=donate`);
|
||||
|
||||
// test if FRU iframe is visible
|
||||
const widgetIframe = page.locator(`iframe[title="Donation Widget"]`);
|
||||
await widgetIframe.waitFor({ state: "visible" });
|
||||
expect(await widgetIframe.count()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Share buttons", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(utility.generateUrl("en", utility.THANK_YOU_PAGE_QUERY));
|
||||
await page.locator("body.react-loaded");
|
||||
await waitForImagesToLoad(page);
|
||||
|
||||
// test if donation modal is visible
|
||||
let modalContent = page.locator(`.modal-content`);
|
||||
await modalContent.waitFor({ state: "visible" });
|
||||
|
||||
// test if donation modal can be closed using the "x" button
|
||||
const closeButton = page.locator(
|
||||
`.modal-content button[data-dismiss="modal"].close`
|
||||
);
|
||||
expect(await closeButton.count()).toBe(1);
|
||||
await closeButton.click();
|
||||
expect(await page.locator(`.modal-content`).isVisible()).toBe(false);
|
||||
});
|
||||
|
||||
test("Copy button", async ({ page }) => {
|
||||
// test if Share section is visible
|
||||
let shareSection = page.locator(`.formassembly-petition-thank-you`);
|
||||
await shareSection.waitFor({ state: "visible" });
|
||||
|
||||
// test if Copy button is visible
|
||||
const copyButton = page.locator(
|
||||
".formassembly-petition-thank-you button.link-share"
|
||||
);
|
||||
expect(await copyButton.count()).toBe(1);
|
||||
// check if clicking the Copy button copies the current URL (without query params) to the clipboard
|
||||
await copyButton.click();
|
||||
let clipboardText = await page.evaluate("navigator.clipboard.readText()");
|
||||
let url = page.url().split("?")[0];
|
||||
expect(clipboardText).toBe(url);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
module.exports = {
|
||||
TEST_CAMPAIGN_ID: "7017i000000bIgTAAU",
|
||||
FA_PAGE_QUERY: "show_formassembly=true",
|
||||
THANK_YOU_PAGE_QUERY: "thank_you=true",
|
||||
FA_FIELDS: {
|
||||
firstName: `input[name="tfa_28"]`,
|
||||
lastName: `input[name="tfa_30"]`,
|
||||
email: `input[name="tfa_31"]`,
|
||||
privacy: `input[name="tfa_493"]`,
|
||||
comment: `textarea[name="tfa_497"]`,
|
||||
},
|
||||
FA_HIDDEN_FIELDS: {
|
||||
campaignId: `input[name="tfa_1"]`,
|
||||
thankYouUrl: `input[name="tfa_500"]`,
|
||||
sourceUrl: `input[name="tfa_498"]`,
|
||||
lang: `input[name="tfa_499"]`,
|
||||
newsletter: `input[name="tfa_501"]`,
|
||||
},
|
||||
generateUrl: function (locale = "en", queryString = "") {
|
||||
let pageUrl = `http://localhost:8000/${locale}/campaigns/single-page/`;
|
||||
|
||||
return queryString ? `${pageUrl}?${queryString}` : pageUrl;
|
||||
},
|
||||
};
|
Загрузка…
Ссылка в новой задаче