e2eUtils.ts 4.1 KB

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