From 4f15d222221200d2f89ccd914f572e21fc1aa503 Mon Sep 17 00:00:00 2001 From: Liang Gong Date: Wed, 13 Nov 2024 15:36:25 -0800 Subject: [PATCH] fix(all): fix example test scenario and some BE work Summary: Use `void 0` instead of `undefined`, which can be redefined and shadowed in specific scope. It also fixes broken OSS test scenario example (due to example site content change) Differential Revision: D65863882 fbshipit-source-id: 8e318c28bdd776068760f3be23ac2ca5cf0d5e27 --- README.md | 50 +++++++++-------- .../src/__tests__/parser/StringNode.test.ts | 2 +- packages/core/src/lib/HeapAnalyzer.ts | 6 +- packages/core/src/lib/Serializer.ts | 33 +++++------ packages/core/src/lib/Types.ts | 4 +- packages/core/src/lib/Utils.ts | 12 ++-- .../core/src/trace-cluster/TraceBucket.ts | 2 +- packages/e2e/src/BaseSynthesizer.ts | 3 +- packages/e2e/src/ScriptManager.ts | 2 +- .../src/plugins/scenarios/test-google-maps.js | 2 +- .../src/plugins/ObjectShapeAnalysis.ts | 2 +- packages/memlab/README.md | 55 ++++++++++--------- website/docs/api/modules/core_src.md | 4 +- website/docusaurus.config.js | 2 +- website/src/data/HomePageMainTerminal.js | 2 +- website/src/pages/index.tsx | 2 +- 16 files changed, 96 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index def4f2952..c623f25e4 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,18 @@ JavaScript memory leaks and optimization opportunities. **Online Resources:** [[Website and Demo](https://facebook.github.io/memlab)] | [[Documentation](https://facebook.github.io/memlab/docs/intro)] | [[Meta Engineering Blog Post](https://engineering.fb.com/2022/09/12/open-source/memlab/)] Features: - * **Browser memory leak detection** - Write test scenarios with the Puppeteer - API, and memlab will automatically compare JavaScript heap snapshots, filter - out memory leaks, and aggregate the results - * **Object-oriented heap traversing API** - Supports the creation of - self-defined memory leak detector, and enables programmatic analysis JS heap - snapshots taken from Chromium-based browsers, Node.js, Electron.js, and Hermes - * **Memory CLI toolbox** - Built-in toolbox and APIs for finding memory - optimization opportunities (not necessarily just memory leaks) - * **Memory assertions in Node.js** - Enables unit tests or running node.js - programs to take a heap snapshot of their own state, perform self memory - checking, or write advanced memory assertions + +- **Browser memory leak detection** - Write test scenarios with the Puppeteer + API, and memlab will automatically compare JavaScript heap snapshots, filter + out memory leaks, and aggregate the results +- **Object-oriented heap traversing API** - Supports the creation of + self-defined memory leak detector, and enables programmatic analysis JS heap + snapshots taken from Chromium-based browsers, Node.js, Electron.js, and Hermes +- **Memory CLI toolbox** - Built-in toolbox and APIs for finding memory + optimization opportunities (not necessarily just memory leaks) +- **Memory assertions in Node.js** - Enables unit tests or running node.js + programs to take a heap snapshot of their own state, perform self memory + checking, or write advanced memory assertions ## CLI Usage @@ -55,7 +56,7 @@ function url() { // action where we want to detect memory leaks: click the Hotels button async function action(page) { // puppeteer page API - await page.click('button[aria-label="Hotels"]'); + await page.click('text/Hotels'); } // action where we want to go back to the step before: click clear search @@ -101,6 +102,7 @@ MemLab found 46 leak(s) --elements (internal)---> [(object elements)] (array) @182929 [8.3MB] ... ``` + To get readable trace, the web site under test needs to serve non-minified code (or at least minified code with readable variables, function name, and property names on objects). @@ -112,6 +114,7 @@ in Chrome DevTool and search for the leaked object ID (`@182929`). View memory issues detected by memlab based on a single JavaScript heap snapshot taken from Chromium, Hermes, memlab, or any node.js or Electron.js program: + ```bash memlab view-heap --snapshot ``` @@ -129,7 +132,7 @@ object (`node`) allocated by the target interaction. function leakFilter(node, heap) { // ... your leak detector logic // return true to mark the node as a memory leak -}; +} ``` `heap` is the graph representation of the final JavaScript heap snapshot. @@ -139,6 +142,7 @@ For more details, view the ### Heap Analysis and Investigation View which object keeps growing in size during interaction in the previous run: + ```bash memlab analyze unbound-object ``` @@ -153,6 +157,7 @@ Use `memlab analyze` to view all built-in memory analyses. For extension, view the [doc site](https://facebook.github.io/memlab). View retainer trace of a particular object: + ```bash memlab trace --node-id ``` @@ -167,15 +172,15 @@ Use the `memlab` npm package to start a E2E run in browser and detect memory lea const memlab = require('memlab'); const scenario = { - // initial page load url - url: () => 'https://www.google.com/maps/@37.386427,-122.0428214,11z', + // initial page load url + url: () => 'https://www.google.com/maps/@37.386427,-122.0428214,11z', - // action where we want to detect memory leaks - action: async (page) => await page.click('button[aria-label="Hotels"]'), + // action where we want to detect memory leaks + action: async page => await page.click('text/Hotels'), - // action where we want to go back to the step before - back: async (page) => await page.click('[aria-label="Close"]'), -} + // action where we want to go back to the step before + back: async page => await page.click('[aria-label="Close"]'), +}; memlab.run({scenario}); ``` @@ -202,7 +207,7 @@ test('memory test with heap assertion', async () => { let heap: IHeapSnapshot = await takeNodeMinimalHeap(); // call some function that may add references to obj - rabbitHole(obj) + rabbitHole(obj); expect(heap.hasObjectWithClassName('TestObject')).toBe(true); obj = null; @@ -211,7 +216,6 @@ test('memory test with heap assertion', async () => { // if rabbitHole does not have any side effect that // adds new references to obj, then obj can be GCed expect(heap.hasObjectWithClassName('TestObject')).toBe(false); - }, 30000); ``` @@ -240,11 +244,13 @@ NOTE: To run the memlab cli locally, make sure to prefix the memlab command with npx from within the memlab repo e.g. `npx memlab` Run tests: + ```bash npm run test ``` ## License + memlab is MIT licensed, as found in the [LICENSE](LICENSE) file. ## Contributing diff --git a/packages/core/src/__tests__/parser/StringNode.test.ts b/packages/core/src/__tests__/parser/StringNode.test.ts index eb7bd988f..a83b91ffb 100644 --- a/packages/core/src/__tests__/parser/StringNode.test.ts +++ b/packages/core/src/__tests__/parser/StringNode.test.ts @@ -62,7 +62,7 @@ test( let strRepresentation = concatString?.toJSONString() ?? '{}'; let rep = JSON.parse(strRepresentation); expect(rep.type).toBe('concatenated string'); - expect(rep.stringValue).toBe(undefined); + expect(rep.stringValue).toBe(void 0); strRepresentation = concatString?.toStringNode()?.toJSONString() ?? '{}'; rep = JSON.parse(strRepresentation); diff --git a/packages/core/src/lib/HeapAnalyzer.ts b/packages/core/src/lib/HeapAnalyzer.ts index 721064b26..a4c27243d 100644 --- a/packages/core/src/lib/HeapAnalyzer.ts +++ b/packages/core/src/lib/HeapAnalyzer.ts @@ -151,7 +151,7 @@ class MemoryAnalyst { { strategy: config.isMLClustering ? new MLTraceSimilarityStrategy() - : undefined, + : void 0, }, ); info.midLevel( @@ -639,7 +639,7 @@ class MemoryAnalyst { { strategy: config.isMLClustering ? new MLTraceSimilarityStrategy() - : undefined, + : void 0, }, ); info.midLevel(`MemLab found ${clusters.length} leak(s)`); @@ -699,7 +699,7 @@ class MemoryAnalyst { { strategy: config.isMLClustering ? new MLTraceSimilarityStrategy() - : undefined, + : void 0, }, ); return clusters; diff --git a/packages/core/src/lib/Serializer.ts b/packages/core/src/lib/Serializer.ts index 41ebbc210..f72a89508 100644 --- a/packages/core/src/lib/Serializer.ts +++ b/packages/core/src/lib/Serializer.ts @@ -7,14 +7,7 @@ * @format * @oncall web_perf_infra */ - -import fs from 'fs'; -import chalk from 'chalk'; -import config from './Config'; -import utils from './Utils'; -import info from './Console'; -import PathFinder from '../paths/TraceFinder'; -import { +import type { E2EStepInfo, HeapNodeIdSet, IHeapEdge, @@ -26,7 +19,15 @@ import { LeakTracePathItem, Nullable, Optional, + Undefinable, } from './Types'; + +import fs from 'fs'; +import chalk from 'chalk'; +import config from './Config'; +import utils from './Utils'; +import info from './Console'; +import PathFinder from '../paths/TraceFinder'; import {EdgeIterationCallback} from '..'; import SerializationHelper from './SerializationHelper'; @@ -125,7 +126,7 @@ function JSONifyDetachedHTMLElement( const fiberOptions = {...options}; const nextDepth = options.forceJSONifyDepth ? options.forceJSONifyDepth - 1 - : undefined; + : void 0; fiberOptions.forceJSONifyDepth = nextDepth; // options for elem.__reactProps$xxx @@ -275,7 +276,7 @@ function JSONifyFiberNode( const propsOptions = {...options}; // for FiberNode, force expand a few more levels - if (propsOptions.forceJSONifyDepth === undefined) { + if (propsOptions.forceJSONifyDepth === void 0) { propsOptions.forceJSONifyDepth = 1; } propsOptions.forceJSONifyDepth--; @@ -800,11 +801,7 @@ function summarizeTabsOrder( tabSummaryString = chalk.bold(tabSummaryString); } const tabSummaryStringWithSeparator = `${tabSummaryString} ${sep} `; - if ( - options.color && - options.progress !== undefined && - i > options.progress - ) { + if (options.color && options.progress !== void 0 && i > options.progress) { res += chalk.dim(tabSummaryStringWithSeparator); } else { res += tabSummaryStringWithSeparator; @@ -887,8 +884,8 @@ function summarizePath( let ret = ''; let p: Optional = pathArg; let hasWeakMapEdge = false; - let weakMapKeyObjectId = undefined; - let weakMapEdgeIdx = undefined; + let weakMapKeyObjectId: Undefinable = void 0; + let weakMapEdgeIdx: Undefinable = void 0; while (p) { const node = p.node; @@ -933,7 +930,7 @@ function summarizePath( // if the shortest path contains the same WeakMap edge, // we need to exclude the edge and re-search the shortest path if ( - weakMapEdgeIdx !== undefined && + weakMapEdgeIdx !== void 0 && utils.pathHasEdgeWithIndex(keyNodePath, weakMapEdgeIdx) ) { finder.annotateShortestPaths(snapshot, excludeKeySet); diff --git a/packages/core/src/lib/Types.ts b/packages/core/src/lib/Types.ts index b7c692833..8b18b62de 100644 --- a/packages/core/src/lib/Types.ts +++ b/packages/core/src/lib/Types.ts @@ -176,7 +176,7 @@ export type PuppeteerConfig = LaunchOptions & * // type error here if your local puppeeter version is different * // from the puppeteer used by MemLab * action: async function (page: Page) { - * await page.click('button[aria-label="Hotels"]'); + * await page.click('text/Hotels'); * }, * }, * }; @@ -198,7 +198,7 @@ export type PuppeteerConfig = LaunchOptions & * }, * // no type error here * action: async function (page: Page) { - * await page.click('button[aria-label="Hotels"]'); + * await page.click('text/Hotels'); * }, * }, * }; diff --git a/packages/core/src/lib/Utils.ts b/packages/core/src/lib/Utils.ts index 776d986c6..a177d4b35 100644 --- a/packages/core/src/lib/Utils.ts +++ b/packages/core/src/lib/Utils.ts @@ -400,7 +400,7 @@ function getReactFiberNode(node: Nullable, propName: string) { return; } const targetNode = getToNodeByEdge(node, propName, 'property'); - return isFiberNode(targetNode) ? targetNode : undefined; + return isFiberNode(targetNode) ? targetNode : void 0; } // check if the current node's parent has the node as a child @@ -805,7 +805,7 @@ function getEdgeByNameAndType( return node.findAnyReference( (edge: IHeapEdge) => edge.name_or_index === edgeName && - (type === undefined || edge.type === type), + (type === void 0 || edge.type === type), ); } @@ -935,14 +935,14 @@ function getNumberNodeValue(node: IHeapNode): Nullable { } function getBooleanNodeValue(node: IHeapNode): Nullable { - if (node === null || node === undefined) { + if (node === null || node === void 0) { return null; } if (config.jsEngine === 'hermes') { return node.name === 'true'; } const valueNode = getToNodeByEdge(node, 'value', 'internal'); - if (valueNode === null || valueNode === undefined) { + if (valueNode === null || valueNode === void 0) { return null; } return valueNode.name === 'true'; @@ -1077,7 +1077,7 @@ function getSnapshotSequenceFilePath(): string { } // this should be called only after exploration -function loadTabsOrder(metaFile: Optional = undefined): E2EStepInfo[] { +function loadTabsOrder(metaFile: Optional = void 0): E2EStepInfo[] { try { const file = metaFile != null && fs.existsSync(metaFile) @@ -1203,7 +1203,7 @@ function getReadablePercent(num: number): string { function getReadableBytes(bytes: Optional): string { let n: number, suffix: string; - if (bytes === undefined || bytes === null) { + if (bytes === void 0 || bytes === null) { return ''; } if (bytes >= 1e12) { diff --git a/packages/core/src/trace-cluster/TraceBucket.ts b/packages/core/src/trace-cluster/TraceBucket.ts index 6444fc6a0..8fd5ef190 100644 --- a/packages/core/src/trace-cluster/TraceBucket.ts +++ b/packages/core/src/trace-cluster/TraceBucket.ts @@ -280,7 +280,7 @@ export default class NormalizedTrace { const {allClusters} = NormalizedTrace.diffTraces(leakTraces, [], { strategy: config.isMLClustering ? new MLTraceSimilarityStrategy() - : undefined, + : void 0, }); return NormalizedTrace.clusteredLeakTracesToRecord(allClusters); diff --git a/packages/e2e/src/BaseSynthesizer.ts b/packages/e2e/src/BaseSynthesizer.ts index 87e56562d..3ac1a3423 100644 --- a/packages/e2e/src/BaseSynthesizer.ts +++ b/packages/e2e/src/BaseSynthesizer.ts @@ -22,6 +22,7 @@ import type { CheckPageLoadCallback, Nullable, PageSetupCallback, + Undefinable, } from '@memlab/core'; import type {Page} from 'puppeteer'; @@ -416,7 +417,7 @@ class BaseSynthesizer implements IE2EScenarioSynthesizer { interactions: [scenario.back], } as IE2EStepBasic; - const getRevertStep = (stepType?: string | undefined): E2EStepInfo => + const getRevertStep = (stepType?: Undefinable): E2EStepInfo => scenario.back ? this.getActionFromStep(revertStep, stepType) : this.getAction(SynthesisUtils.revertStep.name, stepType); diff --git a/packages/e2e/src/ScriptManager.ts b/packages/e2e/src/ScriptManager.ts index 98982f7d6..df44392b6 100644 --- a/packages/e2e/src/ScriptManager.ts +++ b/packages/e2e/src/ScriptManager.ts @@ -147,7 +147,7 @@ export default class ScriptManager { this.debounce(() => { fs.writeFileSync( metaFile, - JSON.stringify(this.scriptInfos, undefined, 2), + JSON.stringify(this.scriptInfos, void 0, 2), 'UTF-8', ); }, 1000); diff --git a/packages/e2e/src/plugins/scenarios/test-google-maps.js b/packages/e2e/src/plugins/scenarios/test-google-maps.js index dcfe58520..6df92ba0f 100644 --- a/packages/e2e/src/plugins/scenarios/test-google-maps.js +++ b/packages/e2e/src/plugins/scenarios/test-google-maps.js @@ -16,7 +16,7 @@ function url() { // action where we want to detect memory leaks async function action(page) { - await page.click('button[aria-label="Hotels"]'); + await page.click('text/Hotels'); } // action where we want to go back to the step before diff --git a/packages/heap-analysis/src/plugins/ObjectShapeAnalysis.ts b/packages/heap-analysis/src/plugins/ObjectShapeAnalysis.ts index edc9aa003..7273681bb 100644 --- a/packages/heap-analysis/src/plugins/ObjectShapeAnalysis.ts +++ b/packages/heap-analysis/src/plugins/ObjectShapeAnalysis.ts @@ -90,7 +90,7 @@ class ObjectShapeAnalysis extends BaseAnalysis { const key = serializer.summarizeNodeShape(node); breakdown[key] = breakdown[key] || new Set(); breakdown[key].add(node.id); - if (population[key] === undefined) { + if (population[key] === void 0) { population[key] = {examples: [], n: 0}; } ++population[key].n; diff --git a/packages/memlab/README.md b/packages/memlab/README.md index 9c637536d..b37aa7509 100644 --- a/packages/memlab/README.md +++ b/packages/memlab/README.md @@ -4,22 +4,24 @@ memlab is an end-to-end testing and analysis framework for identifying JavaScript memory leaks and optimization opportunities. Online Resources: -* [Official Website and Demo](https://facebook.github.io/memlab) -* [Documentation](https://facebook.github.io/memlab/docs/intro) -* [Meta Engineering Blog Post](https://engineering.fb.com/2022/09/12/open-source/memlab/) + +- [Official Website and Demo](https://facebook.github.io/memlab) +- [Documentation](https://facebook.github.io/memlab/docs/intro) +- [Meta Engineering Blog Post](https://engineering.fb.com/2022/09/12/open-source/memlab/) Features: - * **Browser memory leak detection** - Write test scenarios with the Puppeteer - API, and memlab will automatically compare JavaScript heap snapshots, filter - out memory leaks, and aggregate the results - * **Object-oriented heap traversing API** - Supports the creation of - self-defined memory leak detector, and enables programmatic analysis JS heap - snapshots taken from Chromium-based browsers, Node.js, Electron.js, and Hermes - * **Memory CLI toolbox** - Built-in toolbox and APIs for finding memory - optimization opportunities (not necessarily just memory leaks) - * **Memory assertions in Node.js** - Enables unit tests or running node.js - programs to take a heap snapshot of their own state, perform self memory - checking, or write advanced memory assertions + +- **Browser memory leak detection** - Write test scenarios with the Puppeteer + API, and memlab will automatically compare JavaScript heap snapshots, filter + out memory leaks, and aggregate the results +- **Object-oriented heap traversing API** - Supports the creation of + self-defined memory leak detector, and enables programmatic analysis JS heap + snapshots taken from Chromium-based browsers, Node.js, Electron.js, and Hermes +- **Memory CLI toolbox** - Built-in toolbox and APIs for finding memory + optimization opportunities (not necessarily just memory leaks) +- **Memory assertions in Node.js** - Enables unit tests or running node.js + programs to take a heap snapshot of their own state, perform self memory + checking, or write advanced memory assertions ## CLI Usage @@ -44,7 +46,7 @@ function url() { // action where we want to detect memory leaks: click the Hotels button async function action(page) { // puppeteer page API - await page.click('button[aria-label="Hotels"]'); + await page.click('text/Hotels'); } // action where we want to go back to the step before: click clear search @@ -90,6 +92,7 @@ MemLab found 46 leak(s) --elements (internal)---> [(object elements)] (array) @182929 [8.3MB] ... ``` + To get readable trace, the web site under test needs to serve non-minified code (or at least minified code with readable variables, function name, and property names on objects). @@ -101,6 +104,7 @@ in Chrome DevTool and search for the leaked object ID (`@182929`). View memory issues detected by memlab based on a single JavaScript heap snapshot taken from Chromium, Hermes, memlab, or any node.js or Electron.js program: + ```bash memlab view-heap --snapshot ``` @@ -116,7 +120,7 @@ object (`node`) allocated by the target interaction. function leakFilter(node, heap) { // ... your leak detector logic // return true to mark the node as a memory leak -}; +} ``` `heap` is the graph representation of the final JavaScript heap snapshot. @@ -126,6 +130,7 @@ For more details, view the ### Heap Analysis and Investigation View which object keeps growing in size during interaction in the previous run: + ```bash memlab analyze unbound-object ``` @@ -140,6 +145,7 @@ Use `memlab analyze` to view all built-in memory analyses. For extension, view the [doc site](https://facebook.github.io/memlab). View retainer trace of a particular object: + ```bash memlab trace --node-id ``` @@ -154,15 +160,15 @@ Use the `memlab` npm package to start a E2E run in browser and detect memory lea const memlab = require('memlab'); const scenario = { - // initial page load url - url: () => 'https://www.google.com/maps/@37.386427,-122.0428214,11z', + // initial page load url + url: () => 'https://www.google.com/maps/@37.386427,-122.0428214,11z', - // action where we want to detect memory leaks - action: async (page) => await page.click('button[aria-label="Hotels"]'), + // action where we want to detect memory leaks + action: async page => await page.click('text/Hotels'), - // action where we want to go back to the step before - back: async (page) => await page.click('[aria-label="Close"]'), -} + // action where we want to go back to the step before + back: async page => await page.click('[aria-label="Close"]'), +}; memlab.run({scenario}); ``` @@ -189,7 +195,7 @@ test('memory test with heap assertion', async () => { let heap: IHeapSnapshot = await takeNodeMinimalHeap(); // call some function that may add references to obj - rabbitHole(obj) + rabbitHole(obj); expect(heap.hasObjectWithClassName('TestObject')).toBe(true); obj = null; @@ -198,7 +204,6 @@ test('memory test with heap assertion', async () => { // if rabbitHole does not have any side effect that // adds new references to obj, then obj can be GCed expect(heap.hasObjectWithClassName('TestObject')).toBe(false); - }, 30000); ``` diff --git a/website/docs/api/modules/core_src.md b/website/docs/api/modules/core_src.md index c0085e2f6..7f93e9586 100644 --- a/website/docs/api/modules/core_src.md +++ b/website/docs/api/modules/core_src.md @@ -203,7 +203,7 @@ const runOptions: RunOptions = { // type error here if your local puppeeter version is different // from the puppeteer used by MemLab action: async function (page: Page) { - await page.click('button[aria-label="Hotels"]'); + await page.click('text/Hotels'); }, }, }; @@ -225,7 +225,7 @@ const runOptions: RunOptions = { }, // no type error here action: async function (page: Page) { - await page.click('button[aria-label="Hotels"]'); + await page.click('text/Hotels'); }, }, }; diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 688f5f4d9..2cb4df3bb 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -168,7 +168,7 @@ const config = { src: 'img/oss_logo.png', href: 'https://opensource.facebook.com', }, - copyright: `Copyright © ${new Date().getFullYear()} Meta Platforms, Inc.`, + copyright: `Copyright © 2022 Meta Platforms, Inc.`, }, algolia: { // The application ID provided by Algolia diff --git a/website/src/data/HomePageMainTerminal.js b/website/src/data/HomePageMainTerminal.js index 5cce3f7a8..17fec2a6e 100644 --- a/website/src/data/HomePageMainTerminal.js +++ b/website/src/data/HomePageMainTerminal.js @@ -589,7 +589,7 @@ const stdouts = [ { time: 90046, content: - "// Visit Google Maps\r\nfunction url() {\r\n return 'https://www.google.com/maps/@37.386427,-122.0428214,11z';\r\n}\r\n\r\n// action where we want to detect memory leaks: click the Hotels button\r\nasync function action(page) {\r\n await page.click('button[aria-label=\"Hotels\"]');\r\n}\r\n\r\n// action where we want to go back to the step before: click clear search\r\nasync function back(page) {\r\n await page.click('[aria-label=\"Clear search\"]');\r\n}\r\n\r\nmodule.exports = {action, back, url};\r\n", + "// Visit Google Maps\r\nfunction url() {\r\n return 'https://www.google.com/maps/@37.386427,-122.0428214,11z';\r\n}\r\n\r\n// action where we want to detect memory leaks: click the Hotels button\r\nasync function action(page) {\r\n await page.click('text/Hotels');\r\n}\r\n\r\n// action where we want to go back to the step before: click clear search\r\nasync function back(page) {\r\n await page.click('[aria-label=\"Close\"]');\r\n}\r\n\r\nmodule.exports = {action, back, url};\r\n", }, {time: 90047, content: ']0;demo@memlab: ~demo@memlab:~$ '}, {time: 95735, content: '\r\n'}, diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index 33f6cb131..7a229cf7c 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -47,7 +47,7 @@ function url() { return 'https://www.google.com/maps/place/Silicon+Valley,+CA/'; } async function action(page) { - await page.click('button[aria-label="Hotels"]'); + await page.click('text/Hotels'); } async function back(page) { await page.click('[aria-label="Close"]');