You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webhook-timeout-error.md 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. # WebhookTimeoutError and Debugging
  2. ## Principle
  3. `WebhookTimeoutError` is thrown when `waitFor` or `waitForCount` does not find a matching webhook within the configured timeout. It carries a snapshot of received webhooks from the last polling cycle — truncated to the last 10 entries — so you can inspect what arrived vs. what was expected. The full count of all received webhooks is available in `totalReceived`.
  4. ## Error Properties
  5. ```typescript
  6. class WebhookTimeoutError extends Error {
  7. readonly name = 'WebhookTimeoutError';
  8. readonly templateName: string; // from webhookTemplate('...')
  9. readonly timeoutMs: number; // the timeout that was exceeded
  10. readonly totalReceived: number; // total webhooks seen in polling window
  11. readonly receivedWebhooks: ReceivedWebhook[]; // last ≤10 received webhooks
  12. readonly matcherDetails: string[]; // human-readable matcher summary
  13. toJSON(): Record<string, unknown>; // serialize all fields for CI logs
  14. }
  15. ```
  16. `receivedWebhooks` is capped at the last 10 entries. If more than 10 webhooks arrived, `totalReceived` shows the full count but `receivedWebhooks` contains only the most recent 10.
  17. ## Reading the Error
  18. The error message format:
  19. ```
  20. Webhook "movie.deleted" not received within 15000ms.
  21. 3 webhook(s) were received but none matched.
  22. Matchers: field(event="movie.deleted"), field(data.id=42).
  23. ```
  24. Use `matcherDetails` to confirm the matchers were configured correctly. Use `receivedWebhooks` to inspect actual payloads — compare field paths and values against what the matchers expect.
  25. ## Validating the Error Shape in Tests
  26. ```typescript
  27. import { WebhookTimeoutError, webhookTemplate } from '@seontechnologies/playwright-utils/webhook';
  28. const neverArrivingTemplate = webhookTemplate('never.arrives')
  29. .matchField('event', 'event.that.never.happens')
  30. .withTimeout(500)
  31. .withInterval(100)
  32. .build();
  33. const [waitResult] = await Promise.allSettled([webhookRegistry.waitFor(neverArrivingTemplate)]);
  34. expect(waitResult.status).toBe('rejected');
  35. if (waitResult.status !== 'rejected') {
  36. throw new Error('Expected webhook wait to reject with WebhookTimeoutError');
  37. }
  38. const error = waitResult.reason as WebhookTimeoutError;
  39. expect(error).toBeInstanceOf(WebhookTimeoutError);
  40. expect(error.templateName).toBe('never.arrives');
  41. expect(error.timeoutMs).toBe(500);
  42. expect(error.toJSON()).toMatchObject({
  43. name: 'WebhookTimeoutError',
  44. templateName: 'never.arrives',
  45. timeoutMs: 500,
  46. totalReceived: expect.any(Number),
  47. matcherDetails: ['field(event="event.that.never.happens")'],
  48. });
  49. ```
  50. ## Inspecting receivedWebhooks
  51. When a webhook arrives but doesn't match, `receivedWebhooks` shows you what actually came in:
  52. ```typescript
  53. // Wait for create webhook first — puts it in the journal
  54. await webhookRegistry.waitFor(movieCreated(movieId));
  55. // Wait for delete webhook that will never arrive — no delete was called
  56. const undeliveredDelete = webhookTemplate<{
  57. event: string;
  58. data: { id: number };
  59. }>('movie.deleted.not.delivered')
  60. .matchField('event', 'movie.deleted')
  61. .matchField('data.id', movieId)
  62. .withTimeout(2_000)
  63. .withInterval(200)
  64. .build();
  65. const [waitResult] = await Promise.allSettled([webhookRegistry.waitFor(undeliveredDelete)]);
  66. expect(waitResult.status).toBe('rejected');
  67. if (waitResult.status !== 'rejected') {
  68. throw new Error('Expected webhook wait to reject with WebhookTimeoutError');
  69. }
  70. const error = waitResult.reason as WebhookTimeoutError;
  71. expect(error).toBeInstanceOf(WebhookTimeoutError);
  72. expect(error.totalReceived).toBeGreaterThanOrEqual(1);
  73. // The movie.created webhook that did arrive is visible in the error
  74. const createdWebhook = error.receivedWebhooks.find((w) => (w.body as { data: { id: number } }).data.id === movieId);
  75. expect(createdWebhook).toBeDefined();
  76. expect((createdWebhook!.body as { event: string }).event).toBe('movie.created');
  77. ```
  78. ## Common Failure Patterns
  79. | What you see | Likely cause | Fix |
  80. | -------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------- |
  81. | `totalReceived: 0` | Webhook not delivered; wrong URL or event not firing | Check application event publishing and webhook routing |
  82. | `totalReceived > 0`, none match | Webhooks arriving but matchers not matching | Inspect `receivedWebhooks[0].body` — check field paths and values |
  83. | `matcherDetails` shows wrong path | Template factory misconfigured | Print `error.toJSON()` and compare paths against actual payload |
  84. | `totalReceived: 0` with `matched-only` | Another worker claimed and deleted the webhook first | Ensure template is scoped by entity ID |
  85. | Parse error in body | Webhook body is not valid JSON | Check `receivedWebhooks[n].parseError` and `rawBody` |
  86. ## matcherDetails Format per Matcher Type
  87. | Matcher | matcherDetails string |
  88. | ------------------------------- | --------------------- |
  89. | `matchField('event', 'x')` | `field(event="x")` |
  90. | `matchPartial({ a: 1 })` | `partial({"a":1})` |
  91. | `matchPredicate('my desc', fn)` | `predicate(my desc)` |
  92. ## Import
  93. ```typescript
  94. import { WebhookTimeoutError } from '@seontechnologies/playwright-utils/webhook';
  95. ```
  96. ## Related Fragments
  97. - `webhook-template-matchers.md` — matcherDetails string format per matcher type
  98. - `webhook-waiting-querying.md` — waitFor and waitForCount throw this error on timeout