-
-
Notifications
You must be signed in to change notification settings - Fork 267
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 e2e convert to pw suite coinmarket #16065
Changes from all commits
7cc0f23
00aaffa
f263ec1
3601439
407409c
9ead4d4
65e1c63
eb3f255
1fa1f97
d75965a
599da41
f3770d8
5c42fbd
237c759
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,24 +22,32 @@ const config: PlaywrightTestConfig = { | |
name: PlaywrightProjects.Desktop, | ||
use: {}, | ||
grepInvert: /@webOnly/, | ||
//TODO: #16073 We cannot set resolution for Electron. Once solved, remove ignoreSnapshots | ||
ignoreSnapshots: true, | ||
}, | ||
], | ||
testDir: 'tests', | ||
workers: 1, // to disable parallelism between test files | ||
use: { | ||
viewport: { width: 1280, height: 720 }, | ||
headless: process.env.HEADLESS === 'true', | ||
trace: 'on', | ||
video: 'on', | ||
screenshot: 'on', | ||
testIdAttribute: 'data-testid', | ||
actionTimeout: 30000, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had some issue with traces and fails. Once I removed this forced timeout, it got fixed. Every action has its default timeout. So I suggest lets leave it to carefully chosen defaults by the playwright devs. |
||
}, | ||
reportSlowTests: null, | ||
reporter: process.env.GITHUB_ACTION | ||
? [['list'], ['@currents/playwright'], ['html', { open: 'never' }]] | ||
: [['list'], ['html', { open: 'never' }]], | ||
timeout: process.env.GITHUB_ACTION ? timeoutCIRun : timeoutLocalRun, | ||
outputDir: path.join(__dirname, 'test-results'), | ||
snapshotPathTemplate: 'snapshots/{projectName}/{testFilePath}/{arg}{ext}', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By default snapshots are stored in folder with test file. Which can lead to cluttered test folders. So this configuration puts snapshot to their own Folder. |
||
expect: { | ||
toHaveScreenshot: { | ||
maxDiffPixelRatio: 0.001, | ||
}, | ||
}, | ||
}; | ||
|
||
// eslint-disable-next-line import/no-default-export | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -105,10 +105,15 @@ export const launchSuite = async (params: LaunchSuiteParams = {}) => { | |
return { electronApp, window }; | ||
}; | ||
|
||
export const isDesktopProject = (testInfo: TestInfo) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I originally wanted to condition the visual comparison by this boolean. but then I found out you can add the --ignore-snapshot to project definition. But anyway I used this new methods on few places so I would keep it as a minor unrelated refactoring. |
||
testInfo.project.name === PlaywrightProjects.Desktop; | ||
|
||
export const isWebProject = (testInfo: TestInfo) => | ||
testInfo.project.name === PlaywrightProjects.Web; | ||
|
||
export const getApiUrl = (webBaseUrl: string | undefined, testInfo: TestInfo) => { | ||
const electronApiURL = 'file:///'; | ||
const apiURL = | ||
testInfo.project.name === PlaywrightProjects.Desktop ? electronApiURL : webBaseUrl; | ||
const apiURL = isDesktopProject(testInfo) ? electronApiURL : webBaseUrl; | ||
if (!apiURL) { | ||
throw new Error('apiURL is not defined'); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { Locator, Page, expect } from '@playwright/test'; | ||
|
||
import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; | ||
import { FiatCurrencyCode } from '@suite-common/suite-config'; | ||
import regional from '@trezor/suite/src/constants/wallet/coinmarket/regional'; | ||
|
||
const getCountryLabel = (country: string) => { | ||
const labelWithFlag = regional.countriesMap.get(country); | ||
if (!labelWithFlag) { | ||
throw new Error(`Country ${country} not found in the countries map`); | ||
} | ||
|
||
return labelWithFlag.substring(labelWithFlag.indexOf(' ') + 1); | ||
}; | ||
|
||
export class MarketActions { | ||
readonly offerSpinner: Locator; | ||
readonly layout: Locator; | ||
readonly form: Locator; | ||
readonly bestOfferProvider: Locator; | ||
readonly bestOfferYouGet: Locator; | ||
readonly bestOfferAmount: Locator; | ||
readonly buyBestOfferButton: Locator; | ||
readonly youPayInput: Locator; | ||
readonly youPayCurrencyDropdown: Locator; | ||
readonly youPayCurrencyOption = (currency: FiatCurrencyCode) => | ||
this.page.getByTestId(`@coinmarket/form/fiat-currency-select/option/${currency}`); | ||
readonly countryOfResidenceDropdown: Locator; | ||
readonly buyOffersPage: Locator; | ||
readonly compareButton: Locator; | ||
readonly quotes: Locator; | ||
readonly quoteOfProvider = (provider: string) => | ||
this.page.getByTestId(`@coinmarket/offers/quote-${provider}`); | ||
readonly quoteProvider: Locator; | ||
readonly quoteAmount: Locator; | ||
readonly selectThisQuoteButton: Locator; | ||
readonly modal: Locator; | ||
readonly buyTermsConfirmButton: Locator; | ||
readonly confirmOnTrezorButton: Locator; | ||
readonly confirmOnDevicePrompt: Locator; | ||
readonly tradeConfirmation: Locator; | ||
readonly tradeConfirmationCryptoAmount: Locator; | ||
readonly tradeConfirmationProvider: Locator; | ||
readonly tradeConfirmationContinueButton: Locator; | ||
|
||
constructor(private page: Page) { | ||
this.offerSpinner = this.page.getByTestId('@coinmarket/offers/loading-spinner'); | ||
this.layout = this.page.getByTestId('@coinmarket'); | ||
this.form = this.page.getByTestId('@coinmarket/form'); | ||
this.bestOfferProvider = this.page.getByTestId('@coinmarket/offers/quote/provider'); | ||
this.bestOfferYouGet = this.page.getByTestId('@coinmarket/best-offer/amount'); | ||
this.bestOfferAmount = this.page.getByTestId('@coinmarket/form/offer/crypto-amount'); | ||
this.buyBestOfferButton = this.page.getByTestId('@coinmarket/form/buy-button'); | ||
this.youPayInput = this.page.getByTestId('@coinmarket/form/fiat-input'); | ||
this.youPayCurrencyDropdown = this.page.getByTestId( | ||
'@coinmarket/form/fiat-currency-select/input', | ||
); | ||
this.countryOfResidenceDropdown = this.page.getByTestId( | ||
'@coinmarket/form/country-select/input', | ||
); | ||
this.buyOffersPage = this.page.getByTestId('@coinmarket/buy-offers'); | ||
this.compareButton = this.page.getByTestId('@coinmarket/form/compare-button'); | ||
this.quotes = this.page.getByTestId('@coinmarket/offers/quote'); | ||
this.quoteProvider = this.page.getByTestId('@coinmarket/offers/quote/provider'); | ||
this.quoteAmount = this.page.getByTestId('@coinmarket/offers/quote/crypto-amount'); | ||
this.selectThisQuoteButton = this.page.getByTestId( | ||
'@coinmarket/offers/get-this-deal-button', | ||
); | ||
this.modal = this.page.getByTestId('@modal'); | ||
this.buyTermsConfirmButton = this.page.getByTestId( | ||
'@coinmarket/buy/offers/buy-terms-confirm-button', | ||
); | ||
this.confirmOnTrezorButton = this.page.getByTestId( | ||
'@coinmarket/offer/confirm-on-trezor-button', | ||
); | ||
this.confirmOnDevicePrompt = this.page.getByTestId('@prompts/confirm-on-device'); | ||
this.tradeConfirmation = this.page.getByTestId('@coinmarket/selected-offer'); | ||
this.tradeConfirmationCryptoAmount = this.page.getByTestId( | ||
'@coinmarket/form/info/crypto-amount', | ||
); | ||
this.tradeConfirmationProvider = this.page.getByTestId('@coinmarket/form/info/provider'); | ||
this.tradeConfirmationContinueButton = this.page.getByTestId( | ||
'@coinmarket/offer/continue-transaction-button', | ||
); | ||
} | ||
|
||
waitForOffersSyncToFinish = async () => { | ||
await expect(this.offerSpinner).toBeHidden({ timeout: 30000 }); | ||
//Even though the offer sync is finished, the best offer might not be displayed correctly yet and show 0 BTC | ||
await expect(this.bestOfferAmount).not.toHaveText('0 BTC'); | ||
await expect(this.buyBestOfferButton).toBeEnabled(); | ||
}; | ||
|
||
selectCountryOfResidence = async (country: string) => { | ||
const countryLabel = getCountryLabel(country); | ||
const currentCountry = await this.countryOfResidenceDropdown.textContent(); | ||
if (currentCountry === countryLabel) { | ||
return; | ||
} | ||
await this.countryOfResidenceDropdown.click(); | ||
await this.countryOfResidenceDropdown.getByRole('combobox').fill(countryLabel); | ||
await this.page.getByTestId(`@coinmarket/form/country-select/option/${country}`).click(); | ||
}; | ||
|
||
selectFiatCurrency = async (currency: FiatCurrencyCode) => { | ||
const currentCurrency = await this.youPayCurrencyDropdown.textContent(); | ||
if (currentCurrency === currency.toUpperCase()) { | ||
return; | ||
} | ||
await this.youPayCurrencyDropdown.click(); | ||
await this.youPayCurrencyOption(currency).click(); | ||
}; | ||
|
||
setYouPayAmount = async ( | ||
amount: string, | ||
currency: FiatCurrencyCode = 'czk', | ||
country: string = 'CZ', | ||
) => { | ||
//Warning: the field is initialized empty and gets default value after the first offer sync | ||
await expect(this.youPayInput).not.toHaveValue(''); | ||
await this.selectCountryOfResidence(country); | ||
await this.selectFiatCurrency(currency); | ||
await this.youPayInput.fill(amount); | ||
//Warning: Bug #16054, as a workaround we wait for offer sync after setting the amount | ||
await this.waitForOffersSyncToFinish(); | ||
}; | ||
|
||
confirmTrade = async () => { | ||
await expect(this.modal).toBeVisible(); | ||
await this.buyTermsConfirmButton.click(); | ||
await this.confirmOnTrezorButton.click(); | ||
await expect(this.confirmOnDevicePrompt).toBeVisible(); | ||
await TrezorUserEnvLink.pressYes(); | ||
await expect(this.confirmOnDevicePrompt).not.toBeVisible(); | ||
}; | ||
|
||
readBestOfferValues = async () => { | ||
await expect(this.bestOfferAmount).not.toHaveText('0 BTC'); | ||
const amount = await this.bestOfferAmount.textContent(); | ||
const provider = await this.bestOfferProvider.textContent(); | ||
if (!amount || !provider) { | ||
throw new Error( | ||
`Test was not able to extract amount or provider from the page. Amount: ${amount}, Provider: ${provider}`, | ||
); | ||
} | ||
|
||
return { amount, provider }; | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Configures the Web tests to be run as nightly. Copied from desktop pipeline