diff --git a/platforms/web/test-e2e/tests/account_test.ts b/platforms/web/test-e2e/tests/account_test.ts index 67048a745..1df8fc270 100644 --- a/platforms/web/test-e2e/tests/account_test.ts +++ b/platforms/web/test-e2e/tests/account_test.ts @@ -14,15 +14,16 @@ const consentCheckbox = 'Yes, I want to receive Blender updates by email'; const firstName = 'John Q.'; const lastName = 'Tester'; +Feature(`account`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); + runTestSuite(testConfigs.jwpSvod, 'JW Player', 'direct', false); runTestSuite(testConfigs.svod, 'Cleeng', 'resetLink', true); function runTestSuite(config: typeof testConfigs.svod, providerName: string, resetPasswordType: string, canEditEmail: boolean) { let loginContext: LoginContext; - Feature(`account - ${providerName}`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(async ({ I }) => { + async function beforeScenario(I: CodeceptJS.I) { + // eslint-disable-next-line react-hooks/rules-of-hooks I.useConfig(config); loginContext = await I.registerOrLogin(loginContext, () => { @@ -34,9 +35,11 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res I.clickCloseButton(); }); - }); + } Scenario(`I can see my account data - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + I.seeInCurrentUrl(constants.baseUrl); await I.openMainMenu(); @@ -73,6 +76,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res Scenario(`I can cancel Edit account - ${providerName}`, async ({ I }) => { if (!canEditEmail) return; + await beforeScenario(I); + editAndCancel(I, editAccount, [ { name: emailField, startingValue: loginContext.email, newValue: 'user@email.nl' }, { name: passwordField, startingValue: '', newValue: 'pass123!?' }, @@ -82,6 +87,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res Scenario(`I get a duplicate email warning - ${providerName}`, async ({ I }) => { if (!canEditEmail) return; + await beforeScenario(I); + editAndCancel(I, editAccount, [ { name: emailField, @@ -100,6 +107,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res Scenario(`I get a wrong password warning - ${providerName}`, async ({ I }) => { if (!canEditEmail) return; + await beforeScenario(I); + editAndCancel(I, editAccount, [ { name: emailField, @@ -118,6 +127,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res Scenario(`I can toggle to view/hide my password - ${providerName}`, async ({ I }) => { if (!canEditEmail) return; + await beforeScenario(I); + I.amOnPage(constants.accountsUrl); I.click(editAccount); await passwordUtils.testPasswordToggling(I, 'confirmationPassword'); @@ -126,6 +137,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res Scenario(`I can reset my password (reset link) - ${providerName}`, async ({ I }) => { if (resetPasswordType !== 'resetlink') return; + await beforeScenario(I); + I.amOnPage(constants.accountsUrl); I.click('Edit password'); @@ -154,6 +167,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res }); Scenario(`I can update firstName - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + editAndSave(I, editDetails, [ { name: firstNameField, @@ -177,6 +192,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res }); Scenario(`I can update lastName - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + editAndSave(I, editDetails, [ { name: lastNameField, @@ -200,6 +217,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res }); Scenario(`I can update details - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + editAndSave(I, editDetails, [ { name: firstNameField, @@ -235,6 +254,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res }); Scenario(`I see name limit errors - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + editAndCancel(I, editDetails, [ { name: firstNameField, @@ -252,6 +273,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res }); Scenario(`I can update my consents - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + I.amOnPage(constants.accountsUrl); I.waitForText('Account info', longTimeout); I.scrollTo('//*[text() = "Legal & Marketing"]', undefined, -100); @@ -288,6 +311,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res Scenario(`I can change email - ${providerName}`, async ({ I }) => { if (!canEditEmail) return; + await beforeScenario(I); + const newEmail = passwordUtils.createRandomEmail(); editAndSave(I, editAccount, [ @@ -305,7 +330,15 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res ]); }); - function editAndSave(I: CodeceptJS.I, editButton: string, fields: { name: string; newValue: string; expectedError?: string }[]) { + function editAndSave( + I: CodeceptJS.I, + editButton: string, + fields: { + name: string; + newValue: string; + expectedError?: string; + }[], + ) { I.amOnPage(constants.accountsUrl); I.waitForElement(`//*[text() = "${editButton}"]`, normalTimeout); I.scrollTo(`//*[text() = "${editButton}"]`); @@ -354,7 +387,16 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res I.click('Cancel'); } - function editAndCancel(I: CodeceptJS.I, editButton: string, fields: { name: string; startingValue: string; newValue: string; expectedError?: string }[]) { + function editAndCancel( + I: CodeceptJS.I, + editButton: string, + fields: { + name: string; + startingValue: string; + newValue: string; + expectedError?: string; + }[], + ) { I.amOnPage(constants.accountsUrl); I.click(editButton); diff --git a/platforms/web/test-e2e/tests/language_test.ts b/platforms/web/test-e2e/tests/language_test.ts index ce844b304..d2244261b 100644 --- a/platforms/web/test-e2e/tests/language_test.ts +++ b/platforms/web/test-e2e/tests/language_test.ts @@ -65,7 +65,7 @@ Scenario('Spanish language is selected when the locale is `es-ES`', async ({ I } await assertActiveLanguage(I, 'es'); }); -Scenario('Changing the language is persisted in the localStorage`', async ({ I }) => { +Scenario('Changing the language is persisted in the localStorage', async ({ I }) => { I.restartBrowser({ locale: 'en-US' }); I.useConfig(testConfigs.basicNoAuth); @@ -80,7 +80,7 @@ Scenario('Changing the language is persisted in the localStorage`', async ({ I } assert.strictEqual(persistedLanguage, 'es'); }); -Scenario('The language is restored from localStorage`', async ({ I }) => { +Scenario('The language is restored from localStorage', async ({ I }) => { I.restartBrowser({ storageState: { origins: [ diff --git a/platforms/web/test-e2e/tests/login/account_test.ts b/platforms/web/test-e2e/tests/login/account_test.ts index 7171f6317..ddce75f24 100644 --- a/platforms/web/test-e2e/tests/login/account_test.ts +++ b/platforms/web/test-e2e/tests/login/account_test.ts @@ -2,28 +2,30 @@ import { testConfigs } from '@jwp/ott-testing/constants'; import constants, { normalTimeout } from '#utils/constants'; import passwordUtils from '#utils/password_utils'; -import { tryToSubmitForm, fillAndCheckField, checkField } from '#utils/login'; +import { checkField, fillAndCheckField, tryToSubmitForm } from '#utils/login'; const fieldRequired = 'This field is required'; const invalidEmail = 'Please re-enter your email details and try again.'; const incorrectLogin = 'Incorrect email/password combination'; -const formFeedback = 'div[class*=formFeedback]'; + +Feature('login - account').retry(Number(process.env.TEST_RETRY_COUNT) || 0); runTestSuite(testConfigs.jwpAuth, 'JW Player'); runTestSuite(testConfigs.cleengAuthvod, 'Cleeng'); function runTestSuite(config: typeof testConfigs.svod, providerName: string) { - Feature(`login - account - ${providerName}`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(async ({ I }) => { + async function beforeScenario(I: CodeceptJS.I) { + // eslint-disable-next-line react-hooks/rules-of-hooks I.useConfig(config); await I.openSignInModal(); I.waitForElement(constants.loginFormSelector, normalTimeout); - }); + } Scenario(`I can close the modal - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + I.clickCloseButton(); I.dontSee('Email'); I.dontSee('Password'); @@ -31,6 +33,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can close the modal by clicking outside - ${providerName}`, async ({ I }) => { + await beforeScenario(I); + I.forceClick('div[data-testid="backdrop"]'); I.dontSee('Email'); @@ -39,10 +43,12 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can toggle to view password - ${providerName}`, async ({ I }) => { + await beforeScenario(I); await passwordUtils.testPasswordToggling(I); }); Scenario(`I get a warning when the form is incompletely filled in - ${providerName}`, async ({ I }) => { + await beforeScenario(I); tryToSubmitForm(I); checkField(I, 'email', fieldRequired); @@ -50,6 +56,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I see email warnings - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.fillField('email', 'danny@email.com'); I.fillField('password', 'Password'); @@ -81,6 +88,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I see empty password warnings - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.fillField('email', 'danny@email.com'); I.fillField('password', 'Password'); @@ -105,6 +113,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I see a login error message - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.fillField('email', 'danny@email.com'); I.fillField('password', 'Password'); diff --git a/platforms/web/test-e2e/tests/login/home_test.ts b/platforms/web/test-e2e/tests/login/home_test.ts index 8dc6f00bb..294327699 100644 --- a/platforms/web/test-e2e/tests/login/home_test.ts +++ b/platforms/web/test-e2e/tests/login/home_test.ts @@ -3,19 +3,16 @@ import { testConfigs } from '@jwp/ott-testing/constants'; import constants, { longTimeout } from '#utils/constants'; import { LoginContext } from '#utils/password_utils'; +Feature(`login - home`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); + runTestSuite(testConfigs.jwpAuth, 'JW Player'); runTestSuite(testConfigs.cleengAuthvod, 'Cleeng'); function runTestSuite(config: typeof testConfigs.svod, providerName: string) { let loginContext: LoginContext; - Feature(`login - home - ${providerName}`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(({ I }) => { - I.useConfig(config); - }); - Scenario(`Sign-in buttons show for accounts config - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openSignInMenu(); I.see('Sign in'); @@ -23,6 +20,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`Sign-in buttons don't show for config without accounts - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openSignInMenu(); I.see('Sign in'); @@ -37,6 +35,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can open the log in modal - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openSignInModal(); I.waitForElement(constants.loginFormSelector, longTimeout); @@ -51,6 +50,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can login - ${providerName}`, async ({ I }) => { + I.useConfig(config); loginContext = await I.registerOrLogin(loginContext); await I.openMainMenu(); @@ -64,6 +64,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can log out - ${providerName}`, async ({ I }) => { + I.useConfig(config); loginContext = await I.registerOrLogin(loginContext); await I.openMainMenu(); diff --git a/platforms/web/test-e2e/tests/payments/coupons_test.ts b/platforms/web/test-e2e/tests/payments/coupons_test.ts index 4d02b77a4..503b4bba2 100644 --- a/platforms/web/test-e2e/tests/payments/coupons_test.ts +++ b/platforms/web/test-e2e/tests/payments/coupons_test.ts @@ -2,7 +2,7 @@ import { testConfigs } from '@jwp/ott-testing/constants'; import { LoginContext } from '#utils/password_utils'; import constants from '#utils/constants'; -import { goToCheckout, formatPrice, finishAndCheckSubscription, addYear, cancelPlan, renewPlan, overrideIP } from '#utils/payments'; +import { goToCheckout, formatPrice, finishSubscription, addYear, cancelPlan, renewPlan, overrideIP, checkSubscription } from '#utils/payments'; import { ProviderProps } from '#test/types'; const jwProps: ProviderProps = { @@ -31,26 +31,24 @@ const cleengProps: ProviderProps = { hasInlineOfferSwitch: false, }; +Feature('payments-coupon').retry(Number(process.env.TEST_RETRY_COUNT) || 0); + +Before(async ({ I }) => { + // This gets used in checkoutService.getOffer to make sure the offers are geolocated for NL + overrideIP(I); +}); + runTestSuite(jwProps, 'JW Player'); runTestSuite(cleengProps, 'Cleeng'); function runTestSuite(props: ProviderProps, providerName: string) { let couponLoginContext: LoginContext; - const today = new Date(); - // This is written as a second test suite so that the login context is a different user. - // Otherwise, there's no way to re-enter payment info and add a coupon code - Feature(`payments-coupon - ${providerName}`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(async ({ I }) => { - // This gets used in checkoutService.getOffer to make sure the offers are geolocated for NL - overrideIP(I); + Scenario(`I can redeem coupons - ${providerName}`, async ({ I }) => { I.useConfig(props.config); couponLoginContext = await I.registerOrLogin(couponLoginContext); - }); - Scenario(`I can redeem coupons - ${providerName}`, async ({ I }) => { await goToCheckout(I); I.click('Redeem coupon'); @@ -88,14 +86,21 @@ function runTestSuite(props: ProviderProps, providerName: string) { ); } - await finishAndCheckSubscription(I, addYear(today), today, props.yearlyOffer.price, props.hasInlineOfferSwitch); + await finishSubscription(I); + await checkSubscription(I, addYear(today), today, props.yearlyOffer.price, props.hasInlineOfferSwitch); }); Scenario(`I can cancel a free subscription - ${providerName}`, async ({ I }) => { - cancelPlan(I, addYear(today), props.canRenewSubscription, providerName); + I.useConfig(props.config); + couponLoginContext = await I.registerOrLogin(couponLoginContext); + + await cancelPlan(I, addYear(today), props.canRenewSubscription, providerName); }); Scenario(`I can renew a free subscription - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + couponLoginContext = await I.registerOrLogin(couponLoginContext); + if (props.canRenewSubscription) { renewPlan(I, addYear(today), props.yearlyOffer.price); } else { diff --git a/platforms/web/test-e2e/tests/payments/payments_test.ts b/platforms/web/test-e2e/tests/payments/payments_test.ts new file mode 100644 index 000000000..0dee7ea68 --- /dev/null +++ b/platforms/web/test-e2e/tests/payments/payments_test.ts @@ -0,0 +1,153 @@ +import { testConfigs } from '@jwp/ott-testing/constants'; + +import { LoginContext } from '#utils/password_utils'; +import constants from '#utils/constants'; +import { goToCheckout, overrideIP } from '#utils/payments'; +import { ProviderProps } from '#test/types'; + +const jwProps: ProviderProps = { + config: testConfigs.jwpSvod, + monthlyOffer: constants.offers.monthlyOffer.inplayer, + yearlyOffer: constants.offers.yearlyOffer.inplayer, + paymentFields: constants.paymentFields.inplayer, + creditCard: constants.creditCard.inplayer, + applicableTax: 0, + canRenewSubscription: false, + canOpenReceipts: false, + fieldWrapper: '', + hasInlineOfferSwitch: true, +}; + +const cleengProps: ProviderProps = { + config: testConfigs.svod, + monthlyOffer: constants.offers.monthlyOffer.cleeng, + yearlyOffer: constants.offers.yearlyOffer.cleeng, + paymentFields: constants.paymentFields.cleeng, + creditCard: constants.creditCard.cleeng, + applicableTax: 21, + canRenewSubscription: true, + canOpenReceipts: false, // Cleeng returns an error on Sandbox making this test flaky + fieldWrapper: 'iframe', + hasInlineOfferSwitch: false, +}; + +Feature('payments').retry(Number(process.env.TEST_RETRY_COUNT) || 0); + +Before(async ({ I }) => { + // This gets used in checkoutService.getOffer to make sure the offers are geolocated for NL + overrideIP(I); +}); + +runTestSuite(jwProps, 'JW Player'); +runTestSuite(cleengProps, 'Cleeng'); + +function runTestSuite(props: ProviderProps, providerName: string) { + let paidLoginContext: LoginContext; + + Scenario(`I can see my payments data - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + + paidLoginContext = await I.registerOrLogin(paidLoginContext); + + await I.openMainMenu(); + + I.click('Payments'); + I.see('Subscription details'); + I.see('You have no subscription. Complete your subscription to start watching all movies and series.'); + I.see('Complete subscription'); + + I.see('Payment method'); + I.see('No payment methods'); + + I.see('Billing history'); + I.see('No transactions'); + }); + + Scenario(`I can see offered subscriptions - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + + paidLoginContext = await I.registerOrLogin(paidLoginContext); + + I.amOnPage(constants.paymentsUrl); + + I.click('Complete subscription'); + I.see('Choose plan'); + I.see('Watch this on JW OTT Web App'); + + await within(props.monthlyOffer.label, () => { + I.see('Monthly'); + I.see('First month free'); + I.see('Cancel anytime'); + I.see('Watch on all devices'); + I.see(props.monthlyOffer.price); + I.see('/month'); + }); + + await within(props.yearlyOffer.label, () => { + I.see('Cancel anytime'); + I.see('Watch on all devices'); + I.see(props.yearlyOffer.price); + I.see('/year'); + }); + + I.see('Continue'); + }); + + Scenario(`I can choose an offer - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + + paidLoginContext = await I.registerOrLogin(paidLoginContext); + + I.amOnPage(constants.offersUrl); + + I.click(props.monthlyOffer.label); + I.seeCssPropertiesOnElements(props.monthlyOffer.label, { color: '#000000' }); + I.seeCssPropertiesOnElements(props.yearlyOffer.label, { color: '#ffffff' }); + + I.click(props.yearlyOffer.label); + I.seeCssPropertiesOnElements(props.monthlyOffer.label, { color: '#ffffff' }); + I.seeCssPropertiesOnElements(props.yearlyOffer.label, { color: '#000000' }); + + I.click('Continue'); + I.waitForLoaderDone(); + + I.see('Yearly subscription'); + I.see(props.yearlyOffer.price); + I.see('/year'); + + I.see('Redeem coupon'); + I.see(props.yearlyOffer.price); + I.dontSee('Payment method fee'); + I.dontSee(props.yearlyOffer.paymentFee); + I.see('Total'); + if (props.applicableTax !== 0) { + I.see('Applicable tax (21%)'); + } + I.clickCloseButton(); + }); + + Scenario(`I can see payment types - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + + paidLoginContext = await I.registerOrLogin(paidLoginContext); + + await goToCheckout(I); + + I.waitForText('Credit card'); + I.waitForText('PayPal'); + I.waitForText(props.paymentFields.creditCardholder); + I.waitForText('Card number'); + I.waitForText('Expiry date'); + I.waitForText('Security code'); + I.waitForText('Continue'); + I.dontSee("Clicking 'continue' will bring you to the PayPal site."); + + I.click('PayPal'); + + I.waitForText("Clicking 'continue' will bring you to the PayPal site."); + I.dontSee('Card number'); + I.dontSee('Expiry date'); + I.dontSee('Security code'); + I.waitForText('Continue'); + }); +} diff --git a/platforms/web/test-e2e/tests/payments/subscription_test.ts b/platforms/web/test-e2e/tests/payments/subscription_test.ts index 1df037a4e..39e0ca3bb 100644 --- a/platforms/web/test-e2e/tests/payments/subscription_test.ts +++ b/platforms/web/test-e2e/tests/payments/subscription_test.ts @@ -2,7 +2,7 @@ import { testConfigs } from '@jwp/ott-testing/constants'; import { LoginContext } from '#utils/password_utils'; import constants, { longTimeout } from '#utils/constants'; -import { goToCheckout, finishAndCheckSubscription, cancelPlan, renewPlan, overrideIP, addYear } from '#utils/payments'; +import { addYear, cancelPlan, checkSubscription, finishSubscription, formatDate, goToCheckout, overrideIP, renewPlan } from '#utils/payments'; import { ProviderProps } from '#test/types'; const jwProps: ProviderProps = { @@ -17,7 +17,6 @@ const jwProps: ProviderProps = { fieldWrapper: '', hasInlineOfferSwitch: true, }; - const cleengProps: ProviderProps = { config: testConfigs.svod, monthlyOffer: constants.offers.monthlyOffer.cleeng, @@ -31,6 +30,13 @@ const cleengProps: ProviderProps = { hasInlineOfferSwitch: false, }; +Feature('subscription').retry(Number(process.env.TEST_RETRY_COUNT) || 0); + +Before(async ({ I }) => { + // This gets used in checkoutService.getOffer to make sure the offers are geolocated for NL + overrideIP(I); +}); + runTestSuite(jwProps, 'JW Player'); runTestSuite(cleengProps, 'Cleeng'); @@ -41,114 +47,9 @@ function runTestSuite(props: ProviderProps, providerName: string) { const cardInfo = Array.of(['Card number', '•••• •••• •••• 1111'], ['Expiry date', '03/2030'], ['Security code', '******']); - Feature(`payments - ${providerName}`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(async ({ I }) => { - // This gets used in checkoutService.getOffer to make sure the offers are geolocated for NL - overrideIP(I); + Scenario(`I can open the PayPal site - ${providerName}`, async ({ I }) => { I.useConfig(props.config); - }); - - Scenario(`I can see my payments data - ${providerName}`, async ({ I }) => { - paidLoginContext = await I.registerOrLogin(paidLoginContext); - - await I.openMainMenu(); - - I.click('Payments'); - I.see('Subscription details'); - I.see('You have no subscription. Complete your subscription to start watching all movies and series.'); - I.see('Complete subscription'); - - I.see('Payment method'); - I.see('No payment methods'); - I.see('Billing history'); - I.see('No transactions'); - }); - - Scenario(`I can see offered subscriptions - ${providerName}`, async ({ I }) => { - paidLoginContext = await I.registerOrLogin(paidLoginContext); - - I.amOnPage(constants.paymentsUrl); - - I.click('Complete subscription'); - I.see('Choose plan'); - I.see('Watch this on JW OTT Web App'); - - await within(props.monthlyOffer.label, () => { - I.see('Monthly'); - I.see('First month free'); - I.see('Cancel anytime'); - I.see('Watch on all devices'); - I.see(props.monthlyOffer.price); - I.see('/month'); - }); - - await within(props.yearlyOffer.label, () => { - I.see('Cancel anytime'); - I.see('Watch on all devices'); - I.see(props.yearlyOffer.price); - I.see('/year'); - }); - - I.see('Continue'); - }); - - Scenario(`I can choose an offer - ${providerName}`, async ({ I }) => { - paidLoginContext = await I.registerOrLogin(paidLoginContext); - - I.amOnPage(constants.offersUrl); - - I.click(props.monthlyOffer.label); - I.seeCssPropertiesOnElements(props.monthlyOffer.label, { color: '#000000' }); - I.seeCssPropertiesOnElements(props.yearlyOffer.label, { color: '#ffffff' }); - - I.click(props.yearlyOffer.label); - I.seeCssPropertiesOnElements(props.monthlyOffer.label, { color: '#ffffff' }); - I.seeCssPropertiesOnElements(props.yearlyOffer.label, { color: '#000000' }); - - I.click('Continue'); - I.waitForLoaderDone(); - - I.see('Yearly subscription'); - I.see(props.yearlyOffer.price); - I.see('/year'); - - I.see('Redeem coupon'); - I.see(props.yearlyOffer.price); - I.dontSee('Payment method fee'); - I.dontSee(props.yearlyOffer.paymentFee); - I.see('Total'); - if (props.applicableTax !== 0) { - I.see('Applicable tax (21%)'); - } - I.clickCloseButton(); - }); - - Scenario(`I can see payment types - ${providerName}`, async ({ I }) => { - paidLoginContext = await I.registerOrLogin(paidLoginContext); - - await goToCheckout(I); - - I.waitForText('Credit card'); - I.waitForText('PayPal'); - I.waitForText(props.paymentFields.creditCardholder); - I.waitForText('Card number'); - I.waitForText('Expiry date'); - I.waitForText('Security code'); - I.waitForText('Continue'); - I.dontSee("Clicking 'continue' will bring you to the PayPal site."); - - I.click('PayPal'); - - I.waitForText("Clicking 'continue' will bring you to the PayPal site."); - I.dontSee('Card number'); - I.dontSee('Expiry date'); - I.dontSee('Security code'); - I.waitForText('Continue'); - }); - - Scenario(`I can open the PayPal site - ${providerName}`, async ({ I }) => { paidLoginContext = await I.registerOrLogin(paidLoginContext); await goToCheckout(I); @@ -162,34 +63,48 @@ function runTestSuite(props: ProviderProps, providerName: string) { }); Scenario(`I can finish my subscription with credit card - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + paidLoginContext = await I.registerOrLogin(paidLoginContext); await goToCheckout(I); - await I.payWithCreditCard( - props.paymentFields.creditCardholder, - props.creditCard, - props.paymentFields.cardNumber, - props.paymentFields.expiryDate, - props.paymentFields.securityCode, - props.fieldWrapper, - ); + const alreadySubscribed = await tryTo(() => { + I.waitForText('Next billing date is on ' + formatDate(today)); + }); + + if (!alreadySubscribed) { + await I.payWithCreditCard( + props.paymentFields.creditCardholder, + props.creditCard, + props.paymentFields.cardNumber, + props.paymentFields.expiryDate, + props.paymentFields.securityCode, + props.fieldWrapper, + ); + + await finishSubscription(I); + } - await finishAndCheckSubscription(I, addYear(today), today, props.yearlyOffer.price, props.hasInlineOfferSwitch); + await checkSubscription(I, addYear(today), today, props.yearlyOffer.price, props.hasInlineOfferSwitch); cardInfo.forEach(([label, value]) => I.seeInField(label, value)); }); Scenario(`I can cancel my subscription - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + paidLoginContext = await I.registerOrLogin(paidLoginContext); - cancelPlan(I, addYear(today), props.canRenewSubscription, providerName); + await cancelPlan(I, addYear(today), props.canRenewSubscription, providerName); // Still see payment info cardInfo.forEach(([label, value]) => I.seeInField(label, value)); }); Scenario(`I can renew my subscription - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + if (props.canRenewSubscription) { paidLoginContext = await I.registerOrLogin(paidLoginContext); renewPlan(I, addYear(today), props.yearlyOffer.price); @@ -197,6 +112,8 @@ function runTestSuite(props: ProviderProps, providerName: string) { }); Scenario(`I can view my invoices - ${providerName}`, async ({ I }) => { + I.useConfig(props.config); + if (props.canRenewSubscription) { paidLoginContext = await I.registerOrLogin(paidLoginContext); I.amOnPage(constants.paymentsUrl); diff --git a/platforms/web/test-e2e/tests/register_test.ts b/platforms/web/test-e2e/tests/register_test.ts index c07a1cd11..7c634fab5 100644 --- a/platforms/web/test-e2e/tests/register_test.ts +++ b/platforms/web/test-e2e/tests/register_test.ts @@ -3,13 +3,14 @@ import { testConfigs } from '@jwp/ott-testing/constants'; import constants, { longTimeout, normalTimeout } from '#utils/constants'; import passwordUtils from '#utils/password_utils'; +Feature(`register`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); + runTestSuite(testConfigs.jwpAuth, 'JW Player'); runTestSuite(testConfigs.cleengAuthvod, 'Cleeng'); function runTestSuite(config: typeof testConfigs.svod, providerName: string) { - Feature(`register - ${providerName}'`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(async ({ I }) => { + async function beforeScenario(I: CodeceptJS.I) { + // eslint-disable-next-line react-hooks/rules-of-hooks I.useConfig(config); if (await I.isMobile()) { @@ -18,9 +19,10 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { I.click('Sign up'); I.waitForElement(constants.registrationFormSelector, normalTimeout); - }); + } Scenario(`I can open the register modal - ${providerName}`, async ({ I }) => { + await beforeScenario(I); await I.seeQueryParams({ u: 'create-account' }); I.see('Email'); @@ -42,6 +44,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can close the modal - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.waitForElement(constants.registrationFormSelector, normalTimeout); I.clickCloseButton(); @@ -55,7 +58,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { I.see('Sign up'); }); - Scenario(`I can switch to the Sign In modal - ${providerName}`, ({ I }) => { + Scenario(`I can switch to the Sign In modal - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.click('Sign in', constants.registrationFormSelector); I.seeElement(constants.loginFormSelector); I.see('Forgot password'); @@ -67,6 +71,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`The Sign up modal will invalidate when directly pressing submit - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.click('Continue'); I.seeElementInDOM('div[class*=formFeedback]'); // This element can be visually hidden through CSS I.seeAttributesOnElements('input[name="email"]', { 'aria-invalid': 'true' }); @@ -74,6 +79,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I get warned when filling in incorrect credentials - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.fillField('Email', 'test'); I.pressKey('Tab'); I.see('Please re-enter your email details'); @@ -95,6 +101,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I get strength feedback when typing in a password - ${providerName}`, async ({ I }) => { + await beforeScenario(I); const textOptions = ['Weak', 'Fair', 'Strong', 'Very strong']; function checkFeedback(password, expectedColor, expectedText) { @@ -114,10 +121,12 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can toggle to view password - ${providerName}`, async ({ I }) => { + await beforeScenario(I); await passwordUtils.testPasswordToggling(I); }); Scenario(`I can't submit without checking required consents - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.fillField('Email', 'test@123.org'); I.fillField('Password', 'pAssword123!'); @@ -131,6 +140,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I get warned for duplicate users - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.fillField('Email', constants.username); I.fillField('Password', 'Password123!'); await I.fillCustomRegistrationFields(); @@ -140,6 +150,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can register - ${providerName}`, async ({ I }) => { + await beforeScenario(I); I.fillField('Email', passwordUtils.createRandomEmail()); I.fillField('Password', passwordUtils.createRandomPassword()); diff --git a/platforms/web/test-e2e/tests/video_detail_test.ts b/platforms/web/test-e2e/tests/video_detail_test.ts index e3469d3ba..dfc52558e 100644 --- a/platforms/web/test-e2e/tests/video_detail_test.ts +++ b/platforms/web/test-e2e/tests/video_detail_test.ts @@ -5,6 +5,8 @@ import { testConfigs } from '@jwp/ott-testing/constants'; import constants, { longTimeout, normalTimeout } from '#utils/constants'; import passwordUtils, { LoginContext } from '#utils/password_utils'; +Feature(`video_detail`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); + runTestSuite(testConfigs.cleengAuthvod, 'Cleeng'); runTestSuite(testConfigs.jwpAuth, 'JW Player'); @@ -14,13 +16,8 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { password: passwordUtils.createRandomPassword(), }; - Feature(`video_detail - ${providerName}`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(({ I }) => { - I.useConfig(config); - }); - Scenario(`Video detail screen loads - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openVideoCard('Agent 327'); I.see('Agent 327'); I.see('2021'); @@ -36,6 +33,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can expand the description (@mobile-only) - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openVideoCard('Agent 327'); function checkHeight(height) { @@ -63,9 +61,13 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { checkHeight('60px'); }); - Scenario(`I can watch a video - ${providerName}`, async ({ I }) => await playBigBuckBunny(I)); + Scenario(`I can watch a video - ${providerName}`, async ({ I }) => { + I.useConfig(config); + await playBigBuckBunny(I); + }); Scenario(`I can return to the video detail screen - ${providerName}`, async ({ I }) => { + I.useConfig(config); await playBigBuckBunny(I); I.click('div[aria-label="Back"]'); @@ -84,6 +86,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can play a trailer - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openVideoCard(constants.elephantsDreamTitle); I.click('Trailer'); @@ -96,6 +99,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can play a trailer without signing in - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openVideoCard(constants.elephantsDreamTitle); I.see(constants.signUpToWatch); @@ -110,6 +114,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can play a video after signing up - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openVideoCard(constants.elephantsDreamTitle); I.see(constants.signUpToWatch); @@ -132,6 +137,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can play a video after signing in - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.openVideoCard(constants.elephantsDreamTitle); I.see(constants.signUpToWatch); @@ -156,6 +162,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { }); Scenario(`I can share the media - ${providerName}`, async ({ I }) => { + I.useConfig(config); await I.enableClipboard(); await I.openVideoCard(constants.elephantsDreamTitle); diff --git a/platforms/web/test-e2e/tests/watch_history/logged_in_test.ts b/platforms/web/test-e2e/tests/watch_history/logged_in_test.ts index 08a3527e4..c793327c2 100644 --- a/platforms/web/test-e2e/tests/watch_history/logged_in_test.ts +++ b/platforms/web/test-e2e/tests/watch_history/logged_in_test.ts @@ -7,19 +7,16 @@ import { LoginContext } from '#utils/password_utils'; const videoLength = 596; const videoTitle = constants.bigBuckBunnyTitle; +Feature('watch_history - logged in').retry(Number(process.env.TEST_RETRY_COUNT) || 0); + runTestSuite(testConfigs.jwpAuth, testConfigs.jwpAuthNoWatchlist, 'JW Player'); runTestSuite(testConfigs.cleengAuthvod, testConfigs.cleengAuthvodNoWatchlist, 'Cleeng'); function runTestSuite(config: typeof testConfigs.svod, configNoWatchlist: typeof testConfigs.jwpAuthNoWatchlist, providerName: string) { let loginContext: LoginContext; - Feature(`watch_history - logged in - ${providerName}`).retry(Number(process.env.TEST_RETRY_COUNT) || 0); - - Before(({ I }) => { - I.useConfig(config); - }); - Scenario(`I can get my watch history when logged in - ${providerName}`, async ({ I }) => { + I.useConfig(config); await registerOrLogin(I); // New user has no continue watching history shelf @@ -39,6 +36,7 @@ function runTestSuite(config: typeof testConfigs.svod, configNoWatchlist: typeof }); Scenario(`I can get my watch history stored to my account after login - ${providerName}`, async ({ I }) => { + I.useConfig(config); I.dontSee(constants.continueWatchingShelfTitle); await I.openVideoCard(videoTitle); @@ -61,6 +59,7 @@ function runTestSuite(config: typeof testConfigs.svod, configNoWatchlist: typeof }); Scenario(`I can see my watch history on the Home screen when logged in - ${providerName}`, async ({ I }) => { + I.useConfig(config); I.dontSee(constants.continueWatchingShelfTitle); await registerOrLogin(I); diff --git a/platforms/web/test-e2e/utils/payments.ts b/platforms/web/test-e2e/utils/payments.ts index df46e5a84..904b1e95b 100644 --- a/platforms/web/test-e2e/utils/payments.ts +++ b/platforms/web/test-e2e/utils/payments.ts @@ -29,13 +29,15 @@ export function formatDate(date: Date) { return new Intl.DateTimeFormat('en-US', { day: 'numeric', month: 'long', year: 'numeric' }).format(date); } -export async function finishAndCheckSubscription(I: CodeceptJS.I, billingDate: Date, today: Date, yearlyPrice: string, hasInlineOfferSwitch: boolean) { +export async function finishSubscription(I: CodeceptJS.I) { I.click('Continue'); I.waitForText(`Welcome to JW OTT Web App (SVOD)`, longTimeout); I.waitForText(`Thank you for subscribing to JW OTT Web App (SVOD). Please enjoy all our content.`); I.click('Start watching'); +} +export async function checkSubscription(I: CodeceptJS.I, billingDate: Date, today: Date, yearlyPrice: string, hasInlineOfferSwitch: boolean) { const transactionText = 'Annual subscription'; // It takes a few seconds for transactions to load, so try and refresh a few times @@ -67,31 +69,38 @@ export async function finishAndCheckSubscription(I: CodeceptJS.I, billingDate: D I.waitForText(formatDate(today)); } -export function cancelPlan(I: CodeceptJS.I, expirationDate: Date, canRenewSubscription: boolean, providerName?: string) { +export async function cancelPlan(I: CodeceptJS.I, expirationDate: Date, canRenewSubscription: boolean, providerName?: string) { I.amOnPage(constants.paymentsUrl); I.waitForLoaderDone(); - if (providerName?.includes('JW')) { - I.waitForElement('[data-testid="change-subscription-button"]', 10); - I.click('[data-testid="change-subscription-button"]'); - } - - I.waitForElement('[data-testid="cancel-subscription-button"]', 10); - I.click('[data-testid="cancel-subscription-button"]'); - I.see('We are sorry to see you go.'); - I.see('You will be unsubscribed from your current plan by clicking the unsubscribe button below.'); - I.see('Unsubscribe'); - // Make sure the cancel button works - I.click('No, thanks'); + // sometimes the response takes too long, and we retry this test while the subscription is canceled + const isAlreadyCancelled = await tryTo(() => { + I.see('This plan will expire on ' + formatDate(expirationDate)); + }); - I.dontSee('This plan will expire'); + if (!isAlreadyCancelled) { + if (providerName?.includes('JW')) { + I.waitForElement('[data-testid="change-subscription-button"]', 10); + I.click('[data-testid="change-subscription-button"]'); + } - I.waitForElement('[data-testid="cancel-subscription-button"]', 10); - I.click('[data-testid="cancel-subscription-button"]'); - I.click('Unsubscribe'); - I.waitForText('Miss you already.', longTimeout); - I.see('You have been successfully unsubscribed. Your current plan will expire on ' + formatDate(expirationDate)); - I.click('Return to profile'); + I.waitForElement('[data-testid="cancel-subscription-button"]', 10); + I.click('[data-testid="cancel-subscription-button"]'); + I.see('We are sorry to see you go.'); + I.see('You will be unsubscribed from your current plan by clicking the unsubscribe button below.'); + I.see('Unsubscribe'); + // Make sure the cancel button works + I.click('No, thanks'); + + I.dontSee('This plan will expire'); + + I.waitForElement('[data-testid="cancel-subscription-button"]', 10); + I.click('[data-testid="cancel-subscription-button"]'); + I.click('Unsubscribe'); + I.waitForText('Miss you already.', longTimeout); + I.see('You have been successfully unsubscribed. Your current plan will expire on ' + formatDate(expirationDate)); + I.click('Return to profile'); + } if (canRenewSubscription) { I.waitForText('Renew subscription');