playwright/packages/playwright-core/src/server/ariaSnapshot.ts

141 lines
4.6 KiB
TypeScript
Raw Normal View History

/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { AriaTemplateNode } from './injected/ariaSnapshot';
import { yaml } from '../utilsBundle';
import type { AriaRole } from '@injected/roleUtils';
import { assert } from '../utils';
export function parseAriaSnapshot(text: string): AriaTemplateNode {
const fragment = yaml.parse(text) as any[];
const result: AriaTemplateNode = { role: 'fragment' };
populateNode(result, fragment);
return result;
}
function populateNode(node: AriaTemplateNode, container: any[]) {
for (const object of container) {
if (typeof object === 'string') {
const childNode = parseKey(object);
node.children = node.children || [];
node.children.push(childNode);
continue;
}
for (const key of Object.keys(object)) {
const childNode = parseKey(key);
const value = object[key];
node.children = node.children || [];
if (childNode.role === 'text') {
node.children.push(valueOrRegex(value));
continue;
}
if (typeof value === 'string') {
node.children.push({ ...childNode, children: [valueOrRegex(value)] });
continue;
}
node.children.push(childNode);
populateNode(childNode, value);
}
}
}
function applyAttribute(node: AriaTemplateNode, key: string, value: string) {
if (key === 'checked') {
assert(value === 'true' || value === 'false' || value === 'mixed', 'Value of "disabled" attribute must be a boolean or "mixed"');
node.checked = value === 'true' ? true : value === 'false' ? false : 'mixed';
return;
}
if (key === 'disabled') {
assert(value === 'true' || value === 'false', 'Value of "disabled" attribute must be a boolean');
node.disabled = value === 'true';
return;
}
if (key === 'expanded') {
assert(value === 'true' || value === 'false', 'Value of "expanded" attribute must be a boolean');
node.expanded = value === 'true';
return;
}
if (key === 'level') {
assert(!isNaN(Number(value)), 'Value of "level" attribute must be a number');
node.level = Number(value);
return;
}
if (key === 'pressed') {
assert(value === 'true' || value === 'false' || value === 'mixed', 'Value of "pressed" attribute must be a boolean or "mixed"');
node.pressed = value === 'true' ? true : value === 'false' ? false : 'mixed';
return;
}
if (key === 'selected') {
assert(value === 'true' || value === 'false', 'Value of "selected" attribute must be a boolean');
node.selected = value === 'true';
return;
}
throw new Error(`Unsupported attribute [${key}] `);
}
function parseKey(key: string): AriaTemplateNode {
const tokenRegex = /\s*([a-z]+|"(?:[^"]*)"|\/(?:[^\/]*)\/|\[.*?\])/g;
let match;
const tokens = [];
while ((match = tokenRegex.exec(key)) !== null)
tokens.push(match[1]);
if (tokens.length === 0)
throw new Error(`Invalid key ${key}`);
const role = tokens[0] as AriaRole | 'text';
let name: string | RegExp = '';
let index = 1;
if (tokens.length > 1 && (tokens[1].startsWith('"') || tokens[1].startsWith('/'))) {
const nameToken = tokens[1];
if (nameToken.startsWith('"')) {
name = nameToken.slice(1, -1);
} else {
const pattern = nameToken.slice(1, -1);
name = new RegExp(pattern);
}
index = 2;
}
const result: AriaTemplateNode = { role, name };
for (; index < tokens.length; index++) {
const attrToken = tokens[index];
if (attrToken.startsWith('[') && attrToken.endsWith(']')) {
const attrContent = attrToken.slice(1, -1).trim();
const [attrName, attrValue] = attrContent.split('=', 2);
const value = attrValue !== undefined ? attrValue.trim() : 'true';
applyAttribute(result, attrName, value);
} else {
throw new Error(`Invalid attribute token ${attrToken} in key ${key}`);
}
}
return result;
}
function normalizeWhitespace(text: string) {
return text.replace(/[\r\n\s\t]+/g, ' ').trim();
}
function valueOrRegex(value: string): string | RegExp {
return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : normalizeWhitespace(value);
}