Skip to content

Commit

Permalink
Merge pull request #27 from chromaui/jwir3/CAP-1204-content-type-exte…
Browse files Browse the repository at this point in the history
…nsions
  • Loading branch information
jwir3 authored Oct 23, 2023
2 parents b6d75ef + 3c12abd commit 46c93b2
Show file tree
Hide file tree
Showing 19 changed files with 1,338 additions and 1,111 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dist/**/*.js
storybook-static/**/*.js

tsup.config.ts
tsup.config.ts
playwright.config.ts
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ module.exports = {
},
},
{
files: ['**/*.test.ts'],
files: ['**/*.test.ts', '**/*.spec.ts'],
rules: {
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'no-restricted-syntax': 'off',
},
parserOptions: {
project: ['tsconfig.json'],
},
},
],
parserOptions: {
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
- name: Prepare repository
run: git fetch --unshallow --tags

- name: Use Node.js 14.x
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x

- name: Install dependencies
uses: bahmutov/npm-install@v1
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: Use Node.js 14.x
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x

- name: Install dependencies
uses: bahmutov/npm-install@v1

- name: Install Playwright
run: |
yarn playwright install
- name: Run lint
run: |
yarn lint
- name: Run tests
run: |
yarn test
yarn test:unit
yarn test:playwright
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ build-storybook.log
.cache
yarn-error.log

test-archives
test-archives
/test-results/
/playwright-report/
/playwright/.cache/
11 changes: 11 additions & 0 deletions __playwright-tests__/conflict.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, expect } from '../src';

test('it should not break when there is a file and a directory with the same name', async ({
page,
}) => {
await page.goto('/conflict');
const imgLocator1 = page.locator('#cloudImg');
const imgLocator2 = page.locator('#toonImg');
expect(imgLocator1).not.toBeNull();
expect(imgLocator2).not.toBeNull();
});
Binary file added __playwright-tests__/fixtures/another.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __playwright-tests__/fixtures/cloud.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions __playwright-tests__/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const path = require('path');
// eslint-disable-next-line import/no-extraneous-dependencies
const express = require('express');

const app = express();
const port = 3000;

const htmlIntro = `<!doctype html><html>`;
const htmlOutro = `</html>`;

app.get('/', (req, res) => {
res.send(`${htmlIntro}<body>testing 1 2 3</body>${htmlOutro}`);
});

app.get('/toolong', (req, res) => {
res.send(
`${htmlIntro}<body><img id="cloudImg" src="/blahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahb"></body>${htmlOutro}`
);
});

app.get(
'/blahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahbblahblahblahblahblahblahblahblahblahblahb',
(req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/cloud.png'));
}
);

app.get('/conflict', (req, res) => {
res.send(`${htmlIntro}<body><img src="/img"></img><img src="/img/another"></body>${htmlOutro}`);
});

app.get('/img', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/cloud.png'));
});

app.get('/img/another', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/another.jpg'));
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
9 changes: 9 additions & 0 deletions __playwright-tests__/toolong.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { test, expect } from '../src';

test('it should not break when a piece of a filename is longer than 256 bytes', async ({
page,
}) => {
await page.goto('/toolong');
const imgLocator = page.locator('#cloudImg');
expect(imgLocator).not.toBeNull();
});
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"prebuild": "yarn clean",
"build": "tsup",
"build:watch": "yarn build --watch",
"test": "jest",
"test:unit": "jest",
"test:playwright": "playwright test",
"test:server": "node __playwright-tests__/server",
"start": "yarn build:watch",
"release": "yarn build && auto shipit",
"lint": "eslint src/*",
Expand All @@ -42,7 +44,7 @@
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.4",
"@jest/types": "^27.0.6",
"@playwright/test": "^1.32.2",
"@playwright/test": "^1.39.0",
"@storybook/eslint-config-storybook": "^3.1.2",
"@testing-library/dom": "^8.1.0",
"@testing-library/react": "^12.0.0",
Expand Down Expand Up @@ -88,6 +90,7 @@
"@chromaui/rrweb-snapshot": "2.0.0-alpha.7-noAbsolute.2",
"@segment/analytics-node": "^1.1.0",
"fs-extra": "^11.1.1",
"mime": "^3.0.0",
"ts-dedent": "^2.2.0"
},
"peerDependencies": {
Expand Down
12 changes: 12 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './__playwright-tests__',
use: { baseURL: 'http://localhost:3000' },

webServer: {
command: 'yarn test:server',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
40 changes: 7 additions & 33 deletions src/playwright-api/takeArchive.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Page, TestInfo } from '@playwright/test';
import { readFileSync } from 'fs';
import type { elementNode, serializedNodeWithId } from '@chromaui/rrweb-snapshot';
import { NodeType } from '@chromaui/rrweb-snapshot';

import dedent from 'ts-dedent';

import { SourceMapper } from '../utils/source-mapper';
import { logger } from '../utils/logger';

const rrweb = readFileSync(
Expand Down Expand Up @@ -47,42 +47,16 @@ async function takeArchive(
rrwebSnapshot.snapshot(document, { noAbsolute: true });
`);

// XXX_jwir3: We go through and filter any of these that would have file names that would be too large.
const sourceMap: Map<string, string> = new Map<string, string>();
if (domSnapshot.childNodes.length !== 0) {
shortenFileNameSrc(domSnapshot.childNodes, sourceMap);
}
// XXX_jwir3: We go through and filter any of these that would have file names that would be too long.
// This is limited to 250 bytes. Technically, the file system is limited to 256 bytes, but
// this gives us 5 bytes for a period and four characters, in the event that we want to
// add a file extension.
const sourceMapper: SourceMapper = new SourceMapper(domSnapshot);
const sourceMap = sourceMapper.shortenFileNamesLongerThan(250).build();

testInfo.attach(name, { contentType, body: JSON.stringify(domSnapshot) });

return Promise.resolve(sourceMap);
}

function shortenFileNameSrc(
input: Array<serializedNodeWithId>,
existingSourceMap: Map<string, string>
): Map<string, string> {
// eslint-disable-next-line no-restricted-syntax
for (const nextChildNode of input) {
if ('attributes' in nextChildNode && 'src' in nextChildNode.attributes) {
const stringBuffer = Buffer.from(nextChildNode.attributes.src as string);
if (stringBuffer.length > 250) {
const shortName: string = stringBuffer.toString('utf-8', 0, 250);
logger.log(`Filename '${stringBuffer}' is too long. Shortening to '${shortName}'`);
existingSourceMap.set(stringBuffer.toString('utf-8'), shortName);
}
}

if (nextChildNode.type === NodeType.Element) {
const childElementNode: elementNode = nextChildNode as elementNode;

if (childElementNode.childNodes.length !== 0) {
shortenFileNameSrc(childElementNode.childNodes, existingSourceMap);
}
}
}

return existingSourceMap;
}

export { takeArchive };
8 changes: 8 additions & 0 deletions src/resource-archive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ArchiveResponse =
statusCode: number;
statusText?: string;
body: Buffer;
contentType: Protocol.Fetch.HeaderEntry;
}
| {
error: Error;
Expand Down Expand Up @@ -114,6 +115,7 @@ class Watcher {
responseStatusCode,
responseStatusText,
responseErrorReason,
responseHeaders,
}: Protocol.Fetch.requestPausedPayload) {
const requestUrl = new URL(request.url);

Expand Down Expand Up @@ -162,13 +164,19 @@ class Watcher {
}
const { body, base64Encoded } = result;

// If the Content-Type header is present, let's capture it.
const contentTypeHeader: Protocol.Fetch.HeaderEntry = responseHeaders.find(
({ name }) => name === 'Content-Type'
);

// No need to capture the response of the top level page request
const isFirstRequest = requestUrl.toString() === this.firstUrl.toString();
if (isLocalRequest && !isFirstRequest) {
this.archive[request.url] = {
statusCode: responseStatusCode,
statusText: responseStatusText,
body: Buffer.from(body, base64Encoded ? 'base64' : 'utf8'),
contentType: contentTypeHeader,
};
}

Expand Down
41 changes: 41 additions & 0 deletions src/utils/source-mapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { elementNode, serializedElementNodeWithId } from '@chromaui/rrweb-snapshot';
import { SourceMapper } from './source-mapper';

describe('SourceMapper', () => {
it('should map a piece of a source that is longer than 5 characters to a truncated version', () => {
const originalObject: elementNode = {
childNodes: [
{
attributes: {
src: '/home/on/the/range',
},
} as unknown as serializedElementNodeWithId,
],
} as elementNode;

const sourceMap = new SourceMapper(originalObject).shortenFileNamesLongerThan(4).build();
const expectedMap = new Map<string, string>();
expectedMap.set('/home/on/the/range', '/home/on/the/rang');
expect(sourceMap).toEqual(expectedMap);
});

it('should map all pieces of a source that are longer than 12 characters to truncated versions', () => {
const originalObject: elementNode = {
childNodes: [
{
attributes: {
src: '/themaninblackfledthrough/the/desert/and/the/gunslingerfollowed',
},
} as unknown as serializedElementNodeWithId,
],
} as elementNode;

const sourceMap = new SourceMapper(originalObject).shortenFileNamesLongerThan(4).build();
const expectedMap = new Map<string, string>();
expectedMap.set(
'/themaninblackfledthrough/the/desert/and/the/gunslingerfollowed',
'/them/the/dese/and/the/guns'
);
expect(sourceMap).toEqual(expectedMap);
});
});
Loading

0 comments on commit 46c93b2

Please sign in to comment.