Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return structured objects from parsers instead of concatenated JS code #599

Open
notriddle opened this issue Apr 2, 2024 · 0 comments
Open

Comments

@notriddle
Copy link
Contributor

notriddle commented Apr 2, 2024

instructions should, instead of containing the JavaScript code, contain objects that get serialized on the harness side and deserialized in the browser. For example, parseAssertCssInner is currently implemented like this:

instructions.push(`\
const { checkCssProperty } = require('command-helpers.js');
async function checkElem(elem) {
const nonMatchingProps = [];
const jsHandle = await elem.evaluateHandle(e => {
const ${varDict} = [${keys}];
const assertComputedStyle = window.getComputedStyle(e${pseudo});
const simple = [];
const computed = [];
const keys = [];
for (const entry of ${varDict}) {
simple.push(e.style[entry]);
computed.push(assertComputedStyle[entry]);
keys.push(entry);
}
return [keys, simple, computed];
});
const [keys, simple, computed] = await jsHandle.jsonValue();
const values = [${values}];
for (const [i, key] of keys.entries()) {
const localErr = [];
checkCssProperty(key, values[i], simple[i], computed[i], localErr);
${indentString(assertCheck, 3)}
}
if (nonMatchingProps.length !== 0) {
const props = nonMatchingProps.join("; ");
throw "The following errors happened (for ${xpath}\`${selector.value}\`): [" + props + "]";
}
}
${getAndSetElements(selector, varName, checkAllElements)}
${extra}`);
return {
'instructions': instructions,
'wait': false,
'checkResult': true,
};

The idea is that, instead of returning actual JS code, the parser returns a plain old javascript object:

return {
    'instructions': {
        'type': 'assertCssInner',
        checkAllElements,
        varName,
        assertFalse,
        propertyDict,
        varDict,
        selector,
    },
    'wait': false,
    'checkResult': true,
};

And then, on the browser side, the actual implementation is a simple function that contains most of what the parser currently returns.

browserUITestInstructions = {};
function dispatch(instruction) {
    return browserUITestInstructions[instruction.type](instruction);
}
browserUITestInstructions.assertCssInner = function({
    checkAllElements,
    varName,
    assertFalse,
    propertyDict,
    varDict,
    selector,
}) {
    const { checkCssProperty } = require('command-helpers.js');
    async function checkElem(elem) {
        const nonMatchingProps = [];
        const jsHandle = await elem.evaluateHandle(e => {
            const varDict = [...propertyDict.keys()];
            const pseudo = !selector.isXPath && selector.pseudo !== null ? selector.pseudo : undefined;
            const assertComputedStyle = window.getComputedStyle(e, pseudo);
            const simple = [];
            const computed = [];
            const keys = [];

           for (const entry of varDict) {
                simple.push(e.style[entry]);
                computed.push(assertComputedStyle[entry]);
                keys.push(entry);
            }
            return [keys, simple, computed];
        });
        const [keys, simple, computed] = await jsHandle.jsonValue();
        const values = [...propertyDict.values()];

        for (const [i, key] of keys.entries()) {
            const localErr = [];
            checkCssProperty(key, values[i], simple[i], computed[i], localErr);
            if (assertFalse) {
                if (localErr.length === 0) {
                    nonMatchingProps.push("assert didn't fail for key `" + key + '`');
                }
            } else {
                nonMatchingProps.push(...localErr);
            }
        }
        if (nonMatchingProps.length !== 0) {
            const props = nonMatchingProps.join("; ");
            const xpath = selector.isXPath ? 'XPath ' : 'selector ';
            throw "The following errors happened (for ${xpath}\`${selector.value}\`): [" + props + "]";
        }
    }
    // deal with getAndSetElements and extras here
}

This means all functions are exported and used by puppeteer directly, and the test harness merely sends serialized POJOs for each instruction. This means sending less code, but more importantly means responsibility for string escaping can be concentrated in one place (you're less likely to invite Bobby Tables into your test case).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant