microsoft/playwright · error · Error

Failed after ${i + 1} attempt(s): ${e}

Error message

Failed after ${i + 1} attempt(s): ${e}

What it means

Thrown by the internal retry loop after the request fails on every attempt. Retries only happen on ECONNRESET (connection reset by peer) and only when maxRetries > 0; once the loop reaches its final attempt it reports how many tries were made and the underlying error.

Source

Thrown at packages/playwright-core/src/server/fetch.ts:301

      setHeader(headers, 'cookie', valueArray.join('; '));
    }
  }

  private async _sendRequestWithRetries(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer, maxRetries?: number): Promise<SendRequestResult> {
    const log: string[] = [];
    maxRetries ??= 0;
    let backoff = 250;
    for (let i = 0; i <= maxRetries; i++) {
      try {
        return await this._sendRequest(progress, log, url, options, postData);
      } catch (e) {
        if (isAbortError(e))
          throw e;
        e = rewriteOpenSSLErrorIfNeeded(e);
        if (maxRetries === 0)
          throw e;
        if (i === maxRetries)
          throw new Error(`Failed after ${i + 1} attempt(s): ${e}`);
        // Retry on connection reset only.
        if (e.code !== 'ECONNRESET')
          throw e;
        const message = `  Received ECONNRESET, will retry after ${backoff}ms.`;
        log.push(message);
        progress.log(message);
        await progress.wait(backoff);
        backoff *= 2;
      }
    }
    throw new Error('Unreachable');
  }

  private async _sendRequest(progress: Progress, log: string[], url: URL, options: SendRequestOptions, postData?: Buffer): Promise<SendRequestResult>{
    const fetchLog = (message: string) => {
      log.push(message);
      progress.log(message);
    };

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Verify the endpoint is reachable and stable with a plain curl/node https request.
  2. Check the server/proxy for connection-reset causes (idle timeouts, keep-alive settings, WAF).
  3. Increase maxRetries only if resets are genuinely transient, otherwise lower it to surface failures faster.
  4. Inspect the wrapped error in the message to see if it is an SSL/TLS issue and fix certificates accordingly.

Example fix

// before
await request.get(url, { maxRetries: 3 }); // 3 ECONNRESETs -> throws
// after: retry with backoff in your code AND validate reachability
for (let i = 0; i < 3; i++) {
  try { return await request.get(url, { maxRetries: 0 }); }
  catch (e) { if (i === 2) throw e; }
}
Defensive patterns

Strategy: retry

Validate before calling

// Reachability pre-check (cheap) before the real request
const net = require('net');
const ok = await new Promise(r => {
  const s = net.connect(443, new URL(url).hostname).setTimeout(2000)
    .on('connect', () => { s.destroy(); r(true); })
    .on('error', () => r(false)).on('timeout', () => { s.destroy(); r(false); });
});

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Calling request.* with maxRetries set to N>0 while the target server or an intermediary load balancer actively resets the TCP connection (ECONNRESET) on every retry. The final message wraps the last ECONNRESET (or rewritten OpenSSL) error.

Common situations: Unstable upstream host behind a flaky proxy/load balancer; keep-alive connections closed server-side mid-request; TLS/SSL renegotiation resets; high-traffic endpoints that drop connections under load.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/f4b008e2edab1caa. Report an issue: GitHub.