intern
                                
                                 intern copied to clipboard
                                
                                    intern copied to clipboard
                            
                            
                            
                        Add locator helper
Currently, to use different location strategies, one must either call find() with the strategy as the first argument or use the find*() methods (findByCssSelector(), etc.). While this works, a more robust approach might be to pass an object to find() which would be created by a helper function. Consider:
import { By } from ‘leadfoot’;
describe(() => {
    it(async ({ remote }) => {
        const element = await remote.find(By.css(‘.my-class’));
    });
});
By would have functions like css(), className(), xpath(), etc. which would return objects that find() would use to set the strategy and value (perhaps something like { strategy: string; value: string; }). The upside of this approach is that the locators could be reused fairly easily.
I like the idea of locator reuse (so the API is less repetitive) that doesn't involve string literals (because...string literals). I do think we might lose some ease-of-use, though; from a discoverability perspective, findElementById is arguably easier to arrive at than find(someLocator, value), which is easier than (in my mind) import {someLocatorFunction} from 'leadfoot/locators'; find(someLocatorFunction(value)).
Or I could be crazy. I'm paranoid about IDE discoverability now. :)
I’m not opposed to leaving find(strategy: string, value: string) as long as the strategy values are typed in the .d.ts file (they aren’t currently). But the reusability of selectors (and the potential for extensibility) could be powerful:
const { describe, it, before } = intern.getPlugin('interface.bdd');
const { expect } = intern.getPlugin('chai');
import { By } from ‘leadfoot’;
import { Remote } from 'intern/lib/executors/Node';
class AppPage {
    private parent = By.css(‘my-app’);
    nav = this.parent.css(‘> nav’);
    dashboard = this.parent.css(‘> dashboard’);
    constructor(private remote: Remote) {}
    async load() {
    	const { remote } = this;
    	await remote.setFindTimeout(5000);
    	await remote.get('dist/index.html');
    	await this.waitForApp();
    }
    async getNav() {
        return await this.remote.find(this.nav);
    }
    async getDashboard {
        return await this.remote.find(this.dashboard);
    }
    async waitForApp() {
        return await this.remote.findDisplayed(this.parent);
    }
    async waitForNav() {
        return await this.remote.findDisplayed(this.nav);
    }
    async waitForDashboard() {
        return await this.remote.findDisplayed(this.dashboard);
    }
}
describe(‘App’, () => {
    let app: AppPage;
    before(async ({ remote }) => {
        app = new AppPage(remote);
        await app.load();
    });
    it(‘should have a dashboard and nav’, async ({ remote }) => {
        expect(await app.getDashboard()).to.exist;
        expect(await app.getNav()).to.exist;
        const links = await remote.findAll(app.nav.css(‘> a[routerLink]’));
        expect(links).to.have.lengthOf(3);
    });
});
I came up with a pretty simple proof of concept today and updated some functional tests to use it. Let me know what you think.
This is looking pretty nice.
Just out of curiosity, why a By constant vs just exporting locator functions from a by or locator module?
import { css } from 'leadfoot/locators';
// ...
private parent = css('my-app');
I was simulating what protractor has, but we don’t need to do that. I think having it in a By constant (or something similarly named) makes it read better:
remote.find(By.css('.thing');
// vs
remote.find(css('.thing'));
Hmm... I suppose it does.