<p dir="auto"><strong>Context:</strong></p>
<ul dir="auto">
<li>Playwright Version: 1.17</li>
<li>Operating System: Mac OS</li>
<li>Node.js version: 16.3</li>
<li>Browser: Chromium</li>
</ul>
<p dir="auto"><strong>Code Snippet</strong></p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// wallet.address is a string
testInfo.attachments.push({
      name: 'address',
      body: wallet.address as any,
      contentType: 'text/plain',
    })

console.log(wallet.address)"><pre class="notranslate"><span class="pl-c">// wallet.address is a string</span>
<span class="pl-s1">testInfo</span><span class="pl-kos">.</span><span class="pl-c1">attachments</span><span class="pl-kos">.</span><span class="pl-en">push</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
      <span class="pl-c1">name</span>: <span class="pl-s">'address'</span><span class="pl-kos">,</span>
      <span class="pl-c1">body</span>: <span class="pl-s1">wallet</span><span class="pl-kos">.</span><span class="pl-c1">address</span> <span class="pl-s1">as</span> <span class="pl-s1">any</span><span class="pl-kos">,</span>
      <span class="pl-c1">contentType</span>: <span class="pl-s">'text/plain'</span><span class="pl-kos">,</span>
    <span class="pl-kos">}</span><span class="pl-kos">)</span>

<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s1">wallet</span><span class="pl-kos">.</span><span class="pl-c1">address</span><span class="pl-kos">)</span></pre></div>
<p dir="auto"><strong>Describe the bug</strong><br>
I'm trying to add an attachment with <code class="notranslate">contentType: 'text/plain'</code> to my html report.<br>
The problem I'm seeing is there are some additional symbols added to my string:<br>
<a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/17785134/144199793-99046243-7b92-434b-a149-6fb812df6a6c.jpg"><img src="https://user-images.githubusercontent.com/17785134/144199793-99046243-7b92-434b-a149-6fb812df6a6c.jpg" alt="telegram-cloud-photo-size-2-5275988481706931671-y" style="max-width: 100%;"></a></p>
<p dir="auto">I also tried to use <code class="notranslate">Buffer.from(wallet.address)</code> as the body expects to see an instance of the Buffer but all I get is this:<br>
<a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/17785134/144200229-6010fda9-38f2-4f64-bd3c-00da20b3c8d4.png"><img src="https://user-images.githubusercontent.com/17785134/144200229-6010fda9-38f2-4f64-bd3c-00da20b3c8d4.png" alt="image" style="max-width: 100%;"></a></p>
<p dir="auto"><strong>Context:</strong><br>
Playwright Version: <code class="notranslate">@playwright/test@1.16.3</code>, ToT (<a class="commit-link" data-hovercard-type="commit" data-hovercard-url="https://github.com/microsoft/playwright/commit/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530/hovercard" href="https://github.com/microsoft/playwright/commit/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530"><tt>3c1aaa5</tt></a>)</p>
<p dir="auto"><strong>Code Snippet</strong></p>
<p dir="auto">Currently, if you have the following in your tests:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="  testInfo.attachments.push({
    name: &quot;example.txt&quot;,
    contentType: &quot;text/plain&quot;,
    body: Buffer.from(&quot;foo&quot;),
  });

  testInfo.attachments.push({
    name: &quot;example.json&quot;,
    contentType: &quot;application/json&quot;,
    body: Buffer.from(JSON.stringify({ foo: 1})),
  });

  console.log(&quot;stdout output example&quot;);
  console.error(&quot;stderr output example&quot;);"><pre class="notranslate"><code class="notranslate">  testInfo.attachments.push({
    name: "example.txt",
    contentType: "text/plain",
    body: Buffer.from("foo"),
  });

  testInfo.attachments.push({
    name: "example.json",
    contentType: "application/json",
    body: Buffer.from(JSON.stringify({ foo: 1})),
  });

  console.log("stdout output example");
  console.error("stderr output example");
</code></pre></div>
<p dir="auto">the HTML Reporter renders the <code class="notranslate">example.txt</code> and <code class="notranslate">example.json</code> as b64 encoded text (but, notably inconsistent, not <code class="notranslate">stdout</code>/<code class="notranslate">stderr</code>):</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/11915034/140476204-9d5d2dea-1332-4035-a506-fda615137c15.png"><img width="963" alt="Screen Shot 2021-11-05 at 12 42 19 AM" src="https://user-images.githubusercontent.com/11915034/140476204-9d5d2dea-1332-4035-a506-fda615137c15.png" style="max-width: 100%;"></a></p>
<p dir="auto">It would be ideal if plaintext attachments were rendered plainly without the encoding so you can easily understand their contents while viewing the HTML Report in the browser. For each of our e2e smoketests, we attach metadata that allows a test reviewer to find items created in the e2e test run in our backend.) Here's what I expected it to look like:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/11915034/140476394-2acc0d69-6195-4a3a-8eb5-8ffd2511d9aa.png"><img width="978" alt="Screen Shot 2021-11-05 at 12 40 08 AM" src="https://user-images.githubusercontent.com/11915034/140476394-2acc0d69-6195-4a3a-8eb5-8ffd2511d9aa.png" style="max-width: 100%;"></a></p>
<p dir="auto">For now, we can log the info to stdout, but it's nice to put it in a standard attachment so it's easy to view.</p>
<p dir="auto">It looks like stdout/stderr have special treatment, while all other <code class="notranslate">body</code> type attachments are base64 encoded:<br>
</p><div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/microsoft/playwright/blob/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530/packages/playwright-test/src/reporters/raw.ts#L248">playwright/packages/playwright-test/src/reporters/raw.ts</a>
    </p>
    <p class="mb-0 color-fg-muted">
         Line 248
      in
      <a data-pjax="true" class="commit-tease-sha" href="/microsoft/playwright/commit/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530">3c1aaa5</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L248" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="248"></td>
          <td id="LC248" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-c1">body</span>: <span class="pl-s1">attachment</span><span class="pl-kos">.</span><span class="pl-c1">body</span><span class="pl-kos">.</span><span class="pl-en">toString</span><span class="pl-kos">(</span><span class="pl-s">'base64'</span><span class="pl-kos">)</span> </td>
        </tr>
    </tbody></table>
  </div>
</div>
<p></p>
<p dir="auto">There's a few alternative's I'd like us to consider:</p>
<ol dir="auto">
<li>Don't always do the above. If <code class="notranslate">body</code> Buffer is specified AND <code class="notranslate">contentType</code> is plaintext-ish (<code class="notranslate">text/*</code>, <code class="notranslate">application/json</code>), just call <code class="notranslate">toString()</code> instead of <code class="notranslate">toString('base64')</code>.</li>
<li>Leave as is, but change <code class="notranslate">_createAttachment</code> like so:
<div class="highlight highlight-source-diff notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="diff --git a/packages/playwright-test/src/reporters/html.ts b/packages/playwright-test/src/reporters/html.ts
index 2768b4cc..565bf6f5 100644
--- a/packages/playwright-test/src/reporters/html.ts
+++ b/packages/playwright-test/src/reporters/html.ts
@@ -390,15 +390,18 @@ class HtmlBuilder {
          };
        }

-        if ((a.name === 'stdout' || a.name === 'stderr') &amp;&amp; a.contentType === 'text/plain') {
-          if (lastAttachment &amp;&amp;
+        if ((a.name === 'stdout' || a.name === 'stderr')) {
+          if (a.contentType === 'text/plain' &amp;&amp; lastAttachment &amp;&amp;
            lastAttachment.name === a.name &amp;&amp;
            lastAttachment.contentType === a.contentType) {
            lastAttachment.body += stripAnsiEscapes(a.body as string);
            return null;
          }
          a.body = stripAnsiEscapes(a.body as string);
+        } else if (a.body &amp;&amp; (a.contentType.toLowerCase().startsWith(&quot;text/&quot;) || a.contentType.toLowerCase() === &quot;application/json&quot;)) {
+          a.body = Buffer.from(a.body, &quot;base64&quot;).toString();
        }"><pre class="notranslate"><span class="pl-c1">diff --git a/packages/playwright-test/src/reporters/html.ts b/packages/playwright-test/src/reporters/html.ts</span>
index 2768b4cc..565bf6f5 100644
<span class="pl-md">--- a/packages/playwright-test/src/reporters/html.ts</span>
<span class="pl-mi1">+++ b/packages/playwright-test/src/reporters/html.ts</span>
<span class="pl-mdr">@@ -390,15 +390,18 @@</span> class HtmlBuilder {
          };
        }

<span class="pl-md"><span class="pl-md">-</span>        if ((a.name === 'stdout' || a.name === 'stderr') &amp;&amp; a.contentType === 'text/plain') {</span>
<span class="pl-md"><span class="pl-md">-</span>          if (lastAttachment &amp;&amp;</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>        if ((a.name === 'stdout' || a.name === 'stderr')) {</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>          if (a.contentType === 'text/plain' &amp;&amp; lastAttachment &amp;&amp;</span>
            lastAttachment.name === a.name &amp;&amp;
            lastAttachment.contentType === a.contentType) {
            lastAttachment.body += stripAnsiEscapes(a.body as string);
            return null;
          }
          a.body = stripAnsiEscapes(a.body as string);
<span class="pl-mi1"><span class="pl-mi1">+</span>        } else if (a.body &amp;&amp; (a.contentType.toLowerCase().startsWith("text/") || a.contentType.toLowerCase() === "application/json")) {</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>          a.body = Buffer.from(a.body, "base64").toString();</span>
        }</pre></div>
</li>
<li>Similar to (2), but instead of in <code class="notranslate">_createAttachment</code>, conditionally decode it in the React Component around this existing line: <div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/microsoft/playwright/blob/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530/packages/playwright-core/src/web/htmlReport/htmlReport.tsx#L326">playwright/packages/playwright-core/src/web/htmlReport/htmlReport.tsx</a>
    </p>
    <p class="mb-0 color-fg-muted">
         Line 326
      in
      <a data-pjax="true" class="commit-tease-sha" href="/microsoft/playwright/commit/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530">3c1aaa5</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L326" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="326"></td>
          <td id="LC326" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-k">return</span> <span class="pl-kos">[</span><span class="pl-c1">&lt;</span><span class="pl-ent">div</span> <span class="pl-c1">className</span><span class="pl-c1">=</span><span class="pl-s">'attachment-body'</span><span class="pl-c1">&gt;</span><span class="pl-kos">{</span><span class="pl-s1">attachment</span><span class="pl-kos">.</span><span class="pl-c1">body</span><span class="pl-kos">}</span><span class="pl-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">div</span><span class="pl-c1">&gt;</span><span class="pl-kos">]</span><span class="pl-kos">;</span> </td>
        </tr>
    </tbody></table>
  </div>
</div>
</li>
<li>Be more explicit. Change <code class="notranslate">TestInfo.body</code> from <code class="notranslate">Buffer | undefined</code> to <code class="notranslate">Buffer | string | undefined</code>. If the user supplies a <code class="notranslate">string</code> instead of a <code class="notranslate">Buffer</code>, update the following line to just use the raw string as the <code class="notranslate">body</code>: <div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/microsoft/playwright/blob/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530/packages/playwright-test/src/reporters/raw.ts#L248">playwright/packages/playwright-test/src/reporters/raw.ts</a>
    </p>
    <p class="mb-0 color-fg-muted">
         Line 248
      in
      <a data-pjax="true" class="commit-tease-sha" href="/microsoft/playwright/commit/3c1aaa5338f9822d39a4c69c4ddd19acc12d0530">3c1aaa5</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L248" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="248"></td>
          <td id="LC248" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-c1">body</span>: <span class="pl-s1">attachment</span><span class="pl-kos">.</span><span class="pl-c1">body</span><span class="pl-kos">.</span><span class="pl-en">toString</span><span class="pl-kos">(</span><span class="pl-s">'base64'</span><span class="pl-kos">)</span> </td>
        </tr>
    </tbody></table>
  </div>
</div>
</li>
</ol>
<p dir="auto">As an aside, it might be worth cleaning up the serialized attachments format so it's easy to tell if they've been base64 encoded or not. For instance, some attachments (<code class="notranslate">stderr</code> / <code class="notranslate">stdout</code>) store non-encoded data currently, while user-attachments are <code class="notranslate">base64</code> encoded, and there's not a programmatic way to tell the difference in this mix—except magically knowing. I think it would make sense to always b64 encode the body (event if it's stderr/stdout) so it's uniform, or introduce an <code class="notranslate">encoding</code> hint (<code class="notranslate">undefined | "base64"</code>) in the serialized format.</p>
<p dir="auto"><a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pavelfeldman/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pavelfeldman">@pavelfeldman</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/dgozman/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/dgozman">@dgozman</a> : What's your take on the expected behavior/rendering here? If it should be plaintext for my examples above (which would greatly help us when going through our Test Reports), then what are your above thoughts on the correct fix? Happy to contribute an MR for one of the suggestions (or an alternative). Running (2) in a staging environment currently to get around the issue.</p>