Skip to content

Commit ba132fc

Browse files
authored
Confirming that .fill triggers required events in SF (#2982)
* Confirming that .fill triggers required events in SF * Cleanup * removing clipboard permissions * reverting value for sf version * Add extra check that card is valid * Added (or moved) remaining tests related to unsupported.card.number.spec.ts * Slow down typing on test that is always flaky (because PAN doesn't fill properly on Webkit) * Removed commented out line * Removed commented out line * Added comments
1 parent b22744f commit ba132fc

File tree

9 files changed

+187
-82
lines changed

9 files changed

+187
-82
lines changed

packages/e2e-playwright/models/card.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,24 @@ class Card extends Base {
137137
await this.cvcInput.waitFor({ state: 'visible' });
138138
}
139139

140+
/**
141+
* Locator.fill:
142+
* - checks field is visible, enabled & editable
143+
* - focuses a field
144+
* - writes to the field's value prop
145+
* - fires an input event
146+
*
147+
* For most of our test cases .fill can be seen to mimic a paste event
148+
*/
140149
async fillCardNumber(cardNumber: string) {
141-
// reason: https://playwright.dev/docs/api/class-locator#locator-type
142-
// use-case when we don't need to inspect keyboard events
143150
await this.cardNumberInput.fill(cardNumber);
144151
}
145152

153+
/**
154+
* Locator.pressSequentially:
155+
* - Focuses the element
156+
* - then sends a keydown, keypress/input, and keyup event for each character in the text.
157+
*/
146158
async typeCardNumber(cardNumber: string) {
147159
await this.cardNumberInput.pressSequentially(cardNumber, { delay: USER_TYPE_DELAY });
148160
}
@@ -160,8 +172,6 @@ class Card extends Base {
160172
}
161173

162174
async fillExpiryDate(expiryDate: string) {
163-
// reason: https://playwright.dev/docs/api/class-locator#locator-type
164-
// use-case when we don't need to inspect keyboard events
165175
await this.expiryDateInput.fill(expiryDate);
166176
}
167177

@@ -170,8 +180,6 @@ class Card extends Base {
170180
}
171181

172182
async fillCvc(cvc: string) {
173-
// reason: https://playwright.dev/docs/api/class-locator#locator-type
174-
// use-case when we don't need to inspect keyboard events
175183
await this.cvcInput.fill(cvc);
176184
}
177185

packages/e2e-playwright/playwright.config.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,7 @@ const config: PlaywrightTestConfig = {
5252
{
5353
name: 'chromium',
5454
use: {
55-
...devices['Desktop Chrome'],
56-
contextOptions: {
57-
// chromium-specific permissions
58-
permissions: ['clipboard-read', 'clipboard-write']
59-
}
55+
...devices['Desktop Chrome']
6056
}
6157
},
6258

packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.kcp.spec.ts

+4-15
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getStoryUrl } from '../../../../utils/getStoryUrl';
33
import { URL_MAP } from '../../../../../fixtures/URL_MAP';
44
import { binLookupMock } from '../../../../../mocks/binLookup/binLookup.mock';
55
import { kcpMockOptionalDateAndCvcWithPanLengthMock } from '../../../../../mocks/binLookup/binLookup.data';
6-
import { REGULAR_TEST_CARD } from '../../../../utils/constants';
6+
import { CARD_WITH_PAN_LENGTH, REGULAR_TEST_CARD } from '../../../../utils/constants';
77

88
const componentConfig = {
99
brands: ['mc', 'visa', 'amex', 'korean_local_card'],
@@ -30,24 +30,13 @@ test.describe('Test how Card Component handles binLookup returning a panLength p
3030
});
3131

3232
test('#2 Paste non KCP PAN and see focus move to date field', async ({ cardWithKCP, page, browserName }) => {
33-
test.skip(browserName === 'webkit', 'This test is not run for Safari because it always fails on the CI due to the "pasting"');
34-
3533
await cardWithKCP.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig }));
3634

3735
await cardWithKCP.isComponentVisible();
3836

39-
// Place focus on the input
40-
await cardWithKCP.cardNumberLabelElement.click();
41-
42-
// Copy text to clipboard
43-
await page.evaluate(() => navigator.clipboard.writeText('4000620000000007')); // Can't use the constant for some reason
44-
45-
await page.waitForTimeout(1000);
46-
47-
// Paste text from clipboard
48-
await page.keyboard.press('ControlOrMeta+V');
49-
50-
await page.waitForTimeout(1000);
37+
// "Paste" number
38+
await cardWithKCP.fillCardNumber(CARD_WITH_PAN_LENGTH);
39+
await page.waitForTimeout(100);
5140

5241
// Expect UI change - expiryDate field has focus
5342
await expect(cardWithKCP.cardNumberInput).not.toBeFocused();

packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.regular.spec.ts

+4-17
Original file line numberDiff line numberDiff line change
@@ -108,34 +108,21 @@ test.describe('Test Card, & binLookup w. panLength property', () => {
108108
// Card out of date
109109
await card.fillExpiryDate('12/90');
110110

111-
await card.typeCardNumber(CARD_WITH_PAN_LENGTH);
112-
113-
await page.waitForTimeout(500);
111+
await card.typeCardNumber(CARD_WITH_PAN_LENGTH, 300);
114112

115113
// Expect UI change - expiryDate field has focus
116114
await expect(card.cardNumberInput).not.toBeFocused();
117115
await expect(card.expiryDateInput).toBeFocused();
118116
});
119117

120118
test('#6 Fill out PAN by **pasting** number & see that that focus moves to expiryDate', async ({ card, page, browserName }) => {
121-
test.skip(browserName === 'webkit', 'This test is not run for Safari because it always fails on the CI due to the "pasting"');
122-
123119
await card.goto(URL_MAP.card);
124120

125121
await card.isComponentVisible();
126122

127-
// Place focus on the input
128-
await card.cardNumberLabelElement.click();
129-
130-
// Copy text to clipboard
131-
await page.evaluate(() => navigator.clipboard.writeText('4000620000000007')); // Can't use the constant for some reason
132-
133-
await page.waitForTimeout(1000);
134-
135-
// Paste text from clipboard
136-
await page.keyboard.press('ControlOrMeta+V');
137-
138-
await page.waitForTimeout(1000);
123+
// "Paste" number
124+
await card.fillCardNumber(CARD_WITH_PAN_LENGTH);
125+
await page.waitForTimeout(100);
139126

140127
// Expect UI change - expiryDate field has focus
141128
await expect(card.cardNumberInput).not.toBeFocused();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { test, expect } from '../../../../../fixtures/card.fixture';
2+
import { getStoryUrl } from '../../../../utils/getStoryUrl';
3+
import { PLCC_NO_LUHN_NO_DATE, PLCC_WITH_LUHN_NO_DATE_WOULD_FAIL_LUHN, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../../../utils/constants';
4+
import { URL_MAP } from '../../../../../fixtures/URL_MAP';
5+
import LANG from '../../../../../../server/translations/en-US.json';
6+
7+
const PAN_ERROR_NOT_VALID = LANG['cc.num.902'];
8+
9+
test.describe('Testing binLookup/plcc/pasting fny: test what happens when cards that do, or do not, require a luhn check, are pasted in', () => {
10+
test('#1 Test that the paste event triggers the correct response and the validation rules are updated accordingly', async ({ card, page }) => {
11+
//
12+
const componentConfig = { brands: ['mc', 'visa', 'amex', 'bcmc', 'synchrony_plcc'] };
13+
14+
await card.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig }));
15+
16+
await card.isComponentVisible();
17+
18+
/**
19+
* Type number that identifies as plcc, no luhn required, but that fails luhn
20+
*/
21+
await card.fillCardNumber(PLCC_WITH_LUHN_NO_DATE_WOULD_FAIL_LUHN);
22+
await page.waitForTimeout(100);
23+
24+
await card.typeExpiryDate(TEST_DATE_VALUE);
25+
await card.typeCvc(TEST_CVC_VALUE);
26+
27+
// Expect the card not to be valid
28+
await card.pay();
29+
30+
await expect(card.cardNumberErrorElement).toBeVisible();
31+
await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_VALID);
32+
33+
// "Paste" number that identifies as plcc, luhn required
34+
await card.fillCardNumber(PLCC_NO_LUHN_NO_DATE);
35+
await page.waitForTimeout(100);
36+
37+
// If correct events have fired expect the card to be valid i.e. no error message when pressing pay
38+
await card.pay();
39+
await expect(card.cardNumberErrorElement).not.toBeVisible();
40+
});
41+
});
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,91 @@
1-
import { test } from '../../../../fixtures/card.fixture';
1+
import { expect, test } from '../../../../fixtures/card.fixture';
2+
import { getStoryUrl } from '../../../utils/getStoryUrl';
3+
import { URL_MAP } from '../../../../fixtures/URL_MAP';
4+
import { PAYMENT_RESULT, REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE, UNKNOWN_BIN_CARD, VISA_CARD } from '../../../utils/constants';
5+
import LANG from '../../../../../server/translations/en-US.json';
6+
7+
const PAN_ERROR_NOT_SUPPORTED = LANG['cc.num.903'];
8+
9+
test('#1 Test that after an unsupported card has been entered we see errors, PASTING in a full supported card clears errors & makes it possible to pay', async ({
10+
card,
11+
page
12+
}) => {
13+
//
14+
const componentConfig = { brands: ['mc'] };
15+
16+
await card.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig }));
17+
18+
await card.isComponentVisible();
19+
20+
// Fill unsupported card
21+
await card.fillCardNumber(VISA_CARD);
22+
await page.waitForTimeout(100);
23+
24+
await card.typeExpiryDate(TEST_DATE_VALUE);
25+
await card.typeCvc(TEST_CVC_VALUE);
26+
27+
await expect(card.cardNumberErrorElement).toBeVisible();
28+
await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_SUPPORTED);
29+
30+
// "Paste" number that is supported
31+
await card.fillCardNumber(REGULAR_TEST_CARD);
32+
await page.waitForTimeout(100);
33+
34+
// If correct events have fired expect the card to not have errors
35+
await expect(card.cardNumberErrorElement).not.toBeVisible();
36+
37+
// And to be valid
38+
await card.pay();
39+
await expect(card.paymentResult).toHaveText(PAYMENT_RESULT.authorised);
40+
});
241

342
test(
4-
'#1 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then PASTE supported card & check UI error is cleared',
5-
async () => {
6-
// Wait for field to appear in DOM
7-
// Fill card field with unsupported number
8-
// Test UI shows "Unsupported card" error
9-
// Past card field with supported number
10-
// Test UI shows "Unsupported card" error has gone
43+
'#2 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then PASTE card not in db & check UI error is cleared',
44+
async ({ card, page }) => {
45+
const componentConfig = { brands: ['mc'] };
46+
47+
await card.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig }));
48+
49+
await card.isComponentVisible();
50+
51+
// Fill unsupported card
52+
await card.fillCardNumber(VISA_CARD);
53+
await page.waitForTimeout(100);
54+
55+
await card.typeExpiryDate(TEST_DATE_VALUE);
56+
await card.typeCvc(TEST_CVC_VALUE);
57+
58+
await expect(card.cardNumberErrorElement).toBeVisible();
59+
await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_SUPPORTED);
60+
61+
// "Paste" number that is unknown
62+
await card.fillCardNumber(UNKNOWN_BIN_CARD);
63+
await page.waitForTimeout(100);
64+
65+
// If correct events have fired expect the card to not have errors
66+
await expect(card.cardNumberErrorElement).not.toBeVisible();
1167
}
1268
);
1369

1470
test(
15-
'#2 Enter number of unsupported card, ' +
16-
'then check UI shows an error ' +
17-
'then press the Pay button ' +
18-
'then check UI shows more errors ' +
19-
'then PASTE supported card & check PAN UI errors are cleared whilst others persist',
20-
async () => {
21-
// Wait for field to appear in DOM
22-
// Fill card field with unsupported number
23-
// Test UI shows "Unsupported card" error
24-
// Click Pay (which will call showValidation on all fields)
25-
// Past card field with supported number
26-
// Test UI shows "Unsupported card" error has gone
27-
// PAN error cleared but other errors persist
28-
}
29-
);
71+
'#3 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then delete PAN & check UI error is cleared',
72+
async ({ card, page }) => {
73+
const componentConfig = { brands: ['mc'] };
3074

31-
test('#3 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then PASTE card not in db check UI error is cleared', async () => {
32-
// Wait for field to appear in DOM
33-
// Fill card field with unsupported number
34-
// Test UI shows "Unsupported card" error
35-
// Past card field with supported number
36-
// Test UI shows "Unsupported card" error has gone
37-
});
75+
await card.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig }));
3876

39-
test('#4 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then delete PAN & check UI error is cleared', async () => {
40-
// Wait for field to appear in DOM
41-
// Fill card field with unsupported number
42-
// Test UI shows "Unsupported card" error
43-
// delete card number
44-
// Test UI shows "Unsupported card" error has gone
45-
});
77+
await card.isComponentVisible();
78+
79+
// Fill unsupported card
80+
await card.fillCardNumber(VISA_CARD);
81+
await page.waitForTimeout(100);
82+
83+
await expect(card.cardNumberErrorElement).toBeVisible();
84+
await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_SUPPORTED);
85+
86+
await page.waitForTimeout(300); // leave time for focus to shift
87+
88+
await card.deleteCardNumber();
89+
await expect(card.cardNumberErrorElement).not.toBeVisible();
90+
}
91+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { test, expect } from '../../../../fixtures/card.fixture';
2+
import { REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../../utils/constants';
3+
import { URL_MAP } from '../../../../fixtures/URL_MAP';
4+
import LANG from '../../../../../server/translations/en-US.json';
5+
6+
const ERROR_ENTER_PAN = LANG['cc.num.900'];
7+
const ERROR_ENTER_DATE = LANG['cc.dat.910'];
8+
const ERROR_ENTER_CVC = LANG['cc.cvc.920'];
9+
10+
test.describe('Card - UI errors', () => {
11+
test('#1 Not filling in card fields should lead to errors, which are cleared when fields are filled', async ({ card, page }) => {
12+
await card.goto(URL_MAP.card);
13+
await card.isComponentVisible();
14+
await card.pay();
15+
16+
// Expect errors
17+
await expect(card.cardNumberErrorElement).toBeVisible();
18+
await expect(card.cardNumberErrorElement).toHaveText(ERROR_ENTER_PAN);
19+
20+
await expect(card.expiryDateErrorElement).toBeVisible();
21+
await expect(card.expiryDateErrorElement).toHaveText(ERROR_ENTER_DATE);
22+
23+
await expect(card.cvcErrorElement).toBeVisible();
24+
await expect(card.cvcErrorElement).toHaveText(ERROR_ENTER_CVC);
25+
26+
await page.waitForTimeout(300); // leave time for focus to shift
27+
28+
await card.typeCardNumber(REGULAR_TEST_CARD);
29+
await card.typeExpiryDate(TEST_DATE_VALUE);
30+
await card.typeCvc(TEST_CVC_VALUE);
31+
32+
// Expect no errors
33+
await expect(card.cardNumberErrorElement).not.toBeVisible();
34+
await expect(card.expiryDateErrorElement).not.toBeVisible();
35+
await expect(card.cvcErrorElement).not.toBeVisible();
36+
});
37+
});

packages/e2e-playwright/tests/utils/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export const UNKNOWN_VISA_CARD = '41111111'; // card is now in the test DBs (vis
2424

2525
export const PLCC_NO_LUHN_NO_DATE = '6044100018023838'; // binLookup gives luhn check and date not required
2626
export const PLCC_WITH_LUHN_NO_DATE = '6044141000018769'; // binLookup gives luhn check required but date not required
27-
export const PLCC_NO_LUHN_NO_DATE_WOULD_FAIL_LUHN = '6044100033327222'; // A PAN that identifies as a plcc that doesn't require a luhn check BUT that would fail the luhn check if it was required_
27+
export const PLCC_WITH_LUHN_NO_DATE_WOULD_FAIL_LUHN = '6044141000018768'; // binLookup gives luhn check required, date not required, BUT that will fail the luhn check
28+
export const PLCC_NO_LUHN_NO_DATE_WOULD_FAIL_LUHN = '6044100033327222'; // A PAN that identifies as a plcc that doesn't require a luhn check BUT that would fail the luhn check if it was required
2829

2930
// intersolve (plastix)
3031
export const GIFTCARD_NUMBER = '4010100000000000000';

packages/lib/storybook/stories/cards/Card.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const Default: CardStory = {
1919
componentConfiguration: getComponentConfigFromUrl() ?? {
2020
_disableClickToPay: true,
2121
autoFocus: true,
22-
// brands: ['mc'],
22+
// brands: ['mc', 'synchrony_plcc'],
2323
// brandsConfiguration: { visa: { icon: 'http://localhost:3000/nocard.svg', name: 'altVisa' } },
2424
challengeWindowSize: '02',
2525
// configuration: {socialSecurityNumberMode: 'auto'}

0 commit comments

Comments
 (0)