e2eUtils.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import puppeteer, {
  2. type Browser,
  3. type ClickOptions,
  4. type Page,
  5. type PuppeteerLaunchOptions,
  6. } from 'puppeteer'
  7. export const E2E_TIMEOUT: number = 30 * 1000
  8. const puppeteerOptions: PuppeteerLaunchOptions = {
  9. args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
  10. headless: true,
  11. }
  12. const maxTries = 30
  13. export const timeout = (n: number): Promise<any> =>
  14. new Promise(r => setTimeout(r, n))
  15. export async function expectByPolling(
  16. poll: () => Promise<any>,
  17. expected: string,
  18. ): Promise<void> {
  19. for (let tries = 0; tries < maxTries; tries++) {
  20. const actual = (await poll()) || ''
  21. if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
  22. expect(actual).toMatch(expected)
  23. break
  24. } else {
  25. await timeout(50)
  26. }
  27. }
  28. }
  29. export function setupPuppeteer(args?: string[]) {
  30. let browser: Browser
  31. let page: Page
  32. const resolvedOptions = args
  33. ? {
  34. ...puppeteerOptions,
  35. args: [...puppeteerOptions.args!, ...args],
  36. }
  37. : puppeteerOptions
  38. beforeAll(async () => {
  39. browser = await puppeteer.launch(resolvedOptions)
  40. }, 20000)
  41. beforeEach(async () => {
  42. page = await browser.newPage()
  43. await page.evaluateOnNewDocument(() => {
  44. localStorage.clear()
  45. })
  46. page.on('console', e => {
  47. if (e.type() === 'error') {
  48. const err = e.args()[0]
  49. console.error(`Error from Puppeteer-loaded page:\n`, err.remoteObject())
  50. }
  51. })
  52. })
  53. afterEach(async () => {
  54. await page.close()
  55. })
  56. afterAll(async () => {
  57. await browser.close()
  58. })
  59. async function click(selector: string, options?: ClickOptions) {
  60. await page.click(selector, options)
  61. }
  62. async function count(selector: string) {
  63. return (await page.$$(selector)).length
  64. }
  65. async function text(selector: string) {
  66. return await page.$eval(selector, node => node.textContent)
  67. }
  68. async function value(selector: string) {
  69. return await page.$eval(selector, node => (node as HTMLInputElement).value)
  70. }
  71. async function html(selector: string) {
  72. return await page.$eval(selector, node => node.innerHTML)
  73. }
  74. async function classList(selector: string) {
  75. return await page.$eval(selector, (node: any) => [...node.classList])
  76. }
  77. async function children(selector: string) {
  78. return await page.$eval(selector, (node: any) => [...node.children])
  79. }
  80. async function isVisible(selector: string) {
  81. const display = await page.$eval(selector, node => {
  82. return window.getComputedStyle(node).display
  83. })
  84. return display !== 'none'
  85. }
  86. async function isChecked(selector: string) {
  87. return await page.$eval(
  88. selector,
  89. node => (node as HTMLInputElement).checked,
  90. )
  91. }
  92. async function isFocused(selector: string) {
  93. return await page.$eval(selector, node => node === document.activeElement)
  94. }
  95. async function setValue(selector: string, value: string) {
  96. await page.$eval(
  97. selector,
  98. (node, value) => {
  99. ;(node as HTMLInputElement).value = value as string
  100. node.dispatchEvent(new Event('input'))
  101. },
  102. value,
  103. )
  104. }
  105. async function typeValue(selector: string, value: string) {
  106. const el = (await page.$(selector))!
  107. await el.evaluate(node => ((node as HTMLInputElement).value = ''))
  108. await el.type(value)
  109. }
  110. async function enterValue(selector: string, value: string) {
  111. const el = (await page.$(selector))!
  112. await el.evaluate(node => ((node as HTMLInputElement).value = ''))
  113. await el.type(value)
  114. await el.press('Enter')
  115. }
  116. async function clearValue(selector: string) {
  117. return await page.$eval(
  118. selector,
  119. node => ((node as HTMLInputElement).value = ''),
  120. )
  121. }
  122. function timeout(time: number) {
  123. return page.evaluate(time => {
  124. return new Promise(r => {
  125. setTimeout(r, time)
  126. })
  127. }, time)
  128. }
  129. function nextFrame() {
  130. return page.evaluate(() => {
  131. return new Promise(resolve => {
  132. requestAnimationFrame(() => {
  133. requestAnimationFrame(resolve)
  134. })
  135. })
  136. })
  137. }
  138. return {
  139. page: () => page,
  140. click,
  141. count,
  142. text,
  143. value,
  144. html,
  145. classList,
  146. children,
  147. isVisible,
  148. isChecked,
  149. isFocused,
  150. setValue,
  151. typeValue,
  152. enterValue,
  153. clearValue,
  154. timeout,
  155. nextFrame,
  156. }
  157. }