worker-lifecycle.spec.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. const fs = require("node:fs");
  2. const path = require("node:path");
  3. const { test, expect } = require("@playwright/test");
  4. const speedtestSource = fs.readFileSync(
  5. path.join(__dirname, "..", "..", "speedtest.js"),
  6. "utf8"
  7. );
  8. async function installSpeedtest(page) {
  9. await page.goto("about:blank");
  10. await page.evaluate(() => {
  11. window.__workers = [];
  12. window.Worker = class FakeWorker {
  13. constructor(url) {
  14. this.url = url;
  15. this.messages = [];
  16. this.terminated = false;
  17. this.terminateCount = 0;
  18. window.__workers.push(this);
  19. }
  20. postMessage(message) {
  21. this.messages.push(message);
  22. }
  23. terminate() {
  24. this.terminated = true;
  25. this.terminateCount++;
  26. }
  27. emit(data) {
  28. this.onmessage(new MessageEvent("message", { data: JSON.stringify(data) }));
  29. }
  30. };
  31. });
  32. await page.addScriptTag({ content: speedtestSource });
  33. }
  34. function terminalState(testState) {
  35. return { testState: testState };
  36. }
  37. test.describe("Speedtest worker lifecycle", () => {
  38. test.beforeEach(async ({ page }) => {
  39. await installSpeedtest(page);
  40. });
  41. test("terminates a worker after normal completion", async ({ page }) => {
  42. const result = await page.evaluate((data) => {
  43. const speedtest = new Speedtest();
  44. speedtest.start();
  45. const worker = window.__workers[0];
  46. worker.emit(data);
  47. return {
  48. state: speedtest.getState(),
  49. worker: speedtest.worker,
  50. updater: speedtest.updater,
  51. terminated: worker.terminated,
  52. terminateCount: worker.terminateCount
  53. };
  54. }, terminalState(4));
  55. expect(result).toEqual({
  56. state: 4,
  57. worker: null,
  58. updater: null,
  59. terminated: true,
  60. terminateCount: 1
  61. });
  62. });
  63. test("can start again after normal completion", async ({ page }) => {
  64. const result = await page.evaluate((data) => {
  65. const speedtest = new Speedtest();
  66. speedtest.start();
  67. const first = window.__workers[0];
  68. first.emit(data);
  69. speedtest.start();
  70. const second = window.__workers[1];
  71. return {
  72. state: speedtest.getState(),
  73. firstTerminated: first.terminated,
  74. secondTerminated: second.terminated,
  75. activeWorkerIsSecond: speedtest.worker === second
  76. };
  77. }, terminalState(4));
  78. expect(result).toEqual({
  79. state: 3,
  80. firstTerminated: true,
  81. secondTerminated: false,
  82. activeWorkerIsSecond: true
  83. });
  84. });
  85. test("can start again after an abort response", async ({ page }) => {
  86. const result = await page.evaluate((data) => {
  87. const speedtest = new Speedtest();
  88. speedtest.start();
  89. const first = window.__workers[0];
  90. speedtest.abort();
  91. first.emit(data);
  92. speedtest.start();
  93. const second = window.__workers[1];
  94. return {
  95. state: speedtest.getState(),
  96. firstTerminated: first.terminated,
  97. secondTerminated: second.terminated,
  98. activeWorkerIsSecond: speedtest.worker === second
  99. };
  100. }, terminalState(5));
  101. expect(result).toEqual({
  102. state: 3,
  103. firstTerminated: true,
  104. secondTerminated: false,
  105. activeWorkerIsSecond: true
  106. });
  107. });
  108. test("does not terminate a worker started synchronously by onend", async ({ page }) => {
  109. const result = await page.evaluate((data) => {
  110. const speedtest = new Speedtest();
  111. let endCalls = 0;
  112. speedtest.onend = () => {
  113. endCalls++;
  114. speedtest.start();
  115. };
  116. speedtest.start();
  117. const first = window.__workers[0];
  118. first.emit(data);
  119. const second = window.__workers[1];
  120. return {
  121. endCalls: endCalls,
  122. state: speedtest.getState(),
  123. firstTerminated: first.terminated,
  124. secondTerminated: second.terminated,
  125. activeWorkerIsSecond: speedtest.worker === second
  126. };
  127. }, terminalState(4));
  128. expect(result).toEqual({
  129. endCalls: 1,
  130. state: 3,
  131. firstTerminated: true,
  132. secondTerminated: false,
  133. activeWorkerIsSecond: true
  134. });
  135. });
  136. test("forces a single aborted completion when a worker does not respond", async ({ page }) => {
  137. await page.evaluate(() => {
  138. window.__speedtest = new Speedtest();
  139. window.__endCalls = 0;
  140. window.__speedtest.onend = (aborted) => {
  141. if (aborted) window.__endCalls++;
  142. };
  143. window.__speedtest.start();
  144. window.__speedtest.abort();
  145. });
  146. await expect
  147. .poll(() =>
  148. page.evaluate(() => ({
  149. state: window.__speedtest.getState(),
  150. worker: window.__speedtest.worker,
  151. updater: window.__speedtest.updater,
  152. terminated: window.__workers[0].terminated,
  153. endCalls: window.__endCalls
  154. }))
  155. )
  156. .toEqual({
  157. state: 4,
  158. worker: null,
  159. updater: null,
  160. terminated: true,
  161. endCalls: 1
  162. });
  163. await page.waitForTimeout(1100);
  164. expect(await page.evaluate(() => window.__endCalls)).toBe(1);
  165. });
  166. test("ignores delayed events and an old abort timeout after a new run starts", async ({ page }) => {
  167. await page.evaluate((data) => {
  168. window.__speedtest = new Speedtest();
  169. window.__endCalls = 0;
  170. window.__updates = 0;
  171. window.__speedtest.onupdate = () => window.__updates++;
  172. window.__speedtest.onend = () => {
  173. window.__endCalls++;
  174. window.__speedtest.start();
  175. };
  176. window.__speedtest.start();
  177. window.__first = window.__workers[0];
  178. window.__speedtest.abort();
  179. window.__first.emit(data);
  180. window.__second = window.__workers[1];
  181. }, terminalState(5));
  182. await page.waitForTimeout(1100);
  183. const result = await page.evaluate((data) => {
  184. window.__first.emit(data);
  185. return {
  186. endCalls: window.__endCalls,
  187. updates: window.__updates,
  188. state: window.__speedtest.getState(),
  189. firstTerminated: window.__first.terminated,
  190. secondTerminated: window.__second.terminated,
  191. activeWorkerIsSecond: window.__speedtest.worker === window.__second
  192. };
  193. }, terminalState(5));
  194. expect(result).toEqual({
  195. endCalls: 1,
  196. updates: 1,
  197. state: 3,
  198. firstTerminated: true,
  199. secondTerminated: false,
  200. activeWorkerIsSecond: true
  201. });
  202. });
  203. });