Skip to main content
Glama

Chrome DevTools MCP

Official
input.test.ts11.9 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {describe, it} from 'node:test'; import assert from 'assert'; import fs from 'fs/promises'; import path from 'path'; import { click, hover, fill, drag, fillForm, uploadFile, } from '../../src/tools/input.js'; import {html, withBrowser} from '../utils.js'; import {serverHooks} from '../server.js'; describe('input', () => { const server = serverHooks(); describe('click', () => { it('clicks', async () => { await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent( `<!DOCTYPE html><button onclick="this.innerText = 'clicked';">test`, ); await context.createTextSnapshot(); await click.handler( { params: { uid: '1_1', }, }, response, context, ); assert.strictEqual( response.responseLines[0], 'Successfully clicked on the element', ); assert.ok(response.includeSnapshot); assert.ok(await page.$('text/clicked')); }); }); it('double clicks', async () => { await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent( `<!DOCTYPE html><button ondblclick="this.innerText = 'dblclicked';">test`, ); await context.createTextSnapshot(); await click.handler( { params: { uid: '1_1', dblClick: true, }, }, response, context, ); assert.strictEqual( response.responseLines[0], 'Successfully double clicked on the element', ); assert.ok(response.includeSnapshot); assert.ok(await page.$('text/dblclicked')); }); }); it('waits for navigation', async () => { const resolveNavigation = Promise.withResolvers<void>(); server.addHtmlRoute( '/link', html`<a href="/navigated">Navigate page</a>`, ); server.addRoute('/navigated', async (_req, res) => { await resolveNavigation.promise; res.write(html`<main>I was navigated</main>`); res.end(); }); await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.goto(server.getRoute('/link')); await context.createTextSnapshot(); const clickPromise = click.handler( { params: { uid: '1_1', }, }, response, context, ); const [t1, t2] = await Promise.all([ clickPromise.then(() => Date.now()), new Promise<number>(res => { setTimeout(() => { resolveNavigation.resolve(); res(Date.now()); }, 300); }), ]); assert(t1 > t2, 'Waited for navigation'); }); }); it('waits for stable DOM', async () => { server.addHtmlRoute( '/unstable', html` <button>Click to change to see time</button> <script> const button = document.querySelector('button'); button.addEventListener('click', () => { setTimeout(() => { button.textContent = Date.now(); }, 50); }); </script> `, ); await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.goto(server.getRoute('/unstable')); await context.createTextSnapshot(); const handlerResolveTime = await click .handler( { params: { uid: '1_1', }, }, response, context, ) .then(() => Date.now()); const buttonChangeTime = await page.evaluate(() => { const button = document.querySelector('button'); return Number(button?.textContent); }); assert(handlerResolveTime > buttonChangeTime, 'Waited for navigation'); }); }); }); describe('hover', () => { it('hovers', async () => { await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent( `<!DOCTYPE html><button onmouseover="this.innerText = 'hovered';">test`, ); await context.createTextSnapshot(); await hover.handler( { params: { uid: '1_1', }, }, response, context, ); assert.strictEqual( response.responseLines[0], 'Successfully hovered over the element', ); assert.ok(response.includeSnapshot); assert.ok(await page.$('text/hovered')); }); }); }); describe('fill', () => { it('fills out an input', async () => { await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent(`<!DOCTYPE html><input>`); await context.createTextSnapshot(); await fill.handler( { params: { uid: '1_1', value: 'test', }, }, response, context, ); assert.strictEqual( response.responseLines[0], 'Successfully filled out the element', ); assert.ok(response.includeSnapshot); assert.ok(await page.$('text/test')); }); }); }); describe('drags', () => { it('drags one element onto another', async () => { await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent(`<!DOCTYPE html> <div role="button" id="drag" draggable="true">drag me</div> <div id="drop" aria-label="drop" style="width: 100px; height: 100px; border: 1px solid black;" ondrop="this.innerText = 'dropped';"> </div> <script> drag.addEventListener("dragstart", (event) => { event.dataTransfer.setData("text/plain", event.target.id); }); drop.addEventListener("dragover", (event) => { event.preventDefault(); event.dataTransfer.dropEffect = "move"; }); drop.addEventListener("drop", (event) => { event.preventDefault(); const data = event.dataTransfer.getData("text/plain"); event.target.appendChild(document.getElementById(data)); }); </script>`); await context.createTextSnapshot(); await drag.handler( { params: { from_uid: '1_1', to_uid: '1_2', }, }, response, context, ); assert.ok(response.includeSnapshot); assert.strictEqual( response.responseLines[0], 'Successfully dragged an element', ); assert.ok(await page.$('text/dropped')); }); }); }); describe('fill form', () => { it('successfully fills out the form', async () => { await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent(`<!DOCTYPE html> <form> <label>username<input name=username type="text"/></label> <label>email<input name=email type="text"/></label> <input type=submit value="Submit"> </form>`); await context.createTextSnapshot(); await fillForm.handler( { params: { elements: [ { uid: '1_2', value: 'test', }, { uid: '1_4', value: 'test2', }, ], }, }, response, context, ); assert.ok(response.includeSnapshot); assert.strictEqual( response.responseLines[0], 'Successfully filled out the form', ); assert.deepStrictEqual( await page.evaluate(() => { return [ // @ts-expect-error missing types document.querySelector('input[name=username]').value, // @ts-expect-error missing types document.querySelector('input[name=email]').value, ]; }), ['test', 'test2'], ); }); }); }); describe('uploadFile', () => { it('uploads a file to a file input', async () => { const testFilePath = path.join(process.cwd(), 'test.txt'); await fs.writeFile(testFilePath, 'test file content'); await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent(`<!DOCTYPE html> <form> <input type="file" id="file-input"> </form>`); await context.createTextSnapshot(); await uploadFile.handler( { params: { uid: '1_1', filePath: testFilePath, }, }, response, context, ); assert.ok(response.includeSnapshot); assert.strictEqual( response.responseLines[0], `File uploaded from ${testFilePath}.`, ); }); await fs.unlink(testFilePath); }); it('uploads a file when clicking an element opens a file uploader', async () => { const testFilePath = path.join(process.cwd(), 'test.txt'); await fs.writeFile(testFilePath, 'test file content'); await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent(`<!DOCTYPE html> <button id="file-chooser-button">Upload file</button> <input type="file" id="file-input" style="display: none;"> <script> document.getElementById('file-chooser-button').addEventListener('click', () => { document.getElementById('file-input').click(); }); </script>`); await context.createTextSnapshot(); await uploadFile.handler( { params: { uid: '1_1', filePath: testFilePath, }, }, response, context, ); assert.ok(response.includeSnapshot); assert.strictEqual( response.responseLines[0], `File uploaded from ${testFilePath}.`, ); const uploadedFileName = await page.$eval('#file-input', el => { const input = el as HTMLInputElement; return input.files?.[0]?.name; }); assert.strictEqual(uploadedFileName, 'test.txt'); await fs.unlink(testFilePath); }); }); it('throws an error if the element is not a file input and does not open a file chooser', async () => { const testFilePath = path.join(process.cwd(), 'test.txt'); await fs.writeFile(testFilePath, 'test file content'); await withBrowser(async (response, context) => { const page = context.getSelectedPage(); await page.setContent(`<!DOCTYPE html><div>Not a file input</div>`); await context.createTextSnapshot(); await assert.rejects( uploadFile.handler( { params: { uid: '1_1', filePath: testFilePath, }, }, response, context, ), { message: 'Failed to upload file. The element could not accept the file directly, and clicking it did not trigger a file chooser.', }, ); assert.strictEqual(response.responseLines.length, 0); assert.strictEqual(response.includeSnapshot, false); await fs.unlink(testFilePath); }); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ChromeDevTools/chrome-devtools-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server