2

Relatively new to writing end to end tests with Protractor. Also relatively inexperienced at working with promises.

I am writing a test where in some cases I need to loop through my code b/c the record that I select does not meet certain criteria. In those cases I would like to proceed back to a previous step and try another record (and continue doing so until I find a suitable record). I am not able to get my test to enter into my loop though.

I can write regular e2e tests with Protractor, but solving this looping issue is proving difficult. I know it must be because I'm dealing with Promises, and am not handling them correctly. Although I've seen examples of looping through protractor code, they often involve a single method that needs to be done to every item in a list. Here I have multiple steps that need to be done in order to arrive at the point where I can find and set my value to break out of the loop.

Here are some of the threads I've looked at trying to resolve this:

My code as it currently stands:

it('should select a customer who has a valid serial number', () => {
    const products = new HomePage();

    let serialIsValid: boolean = false;
    let selectedElement, serialNumber, product, recordCount, recordList;
    recordList = element.all(by.css(`mat-list.desco-list`));
    recordList.then((records) => {
            recordCount = records.length;
            console.log('records', records.length, 'recordCount', recordCount);
        }
    );

    for (let i = 0; i < recordCount; i++) {
        if (serialIsValid === false) {
            const j = i + 1;
            products.btnFormsSelector.click();
            products.formSelectorRepossession.click();
            browser.wait(EC.visibilityOf(products.itemSearch));
            products.itemSearch.element(by.tagName('input')).sendKeys(browser.params.search_string);
            products.itemSearch.element(by.id('btnSearch')).click();
            browser.wait(EC.visibilityOf(products.itemSearch.element(by.id('list-container'))));
            selectedElement = element(by.tagName(`#itemSearch mat-list:nth-child(${{j}})`));
            selectedElement.click();
            browser.wait(EC.visibilityOf(products.doStuffForm));
            browser.sleep(1000);
            element(by.css('#successful mat-radio-button:nth-child(1) label')).click();
            browser.sleep(1000);
            expect(element(by.css('.itemDetailsContainer'))).toBeTruthy();
            product = products.productIDNumber.getText();
            product.then((item) => {
                serialNumber = item;
                if (item !== 'Unknown') {
                    expect(serialNumber).not.toContain('Unknown');
                    serialIsValid = true;
                } else {
                    i++
                }
            })
        } else {
            console.log('serial is valid: ' + serialIsValid);
            expect(serialNumber).not.toContain('Unknown');
            break;
        }
    }
    console.log('serial number validity: ', serialIsValid);
})

I have rewritten and reorganized my code several times, including trying to break out my code into functions grouping related steps together (as recommended in one of the threads above, and then trying to chain them together them together, like this:

findValidCustomer() {
        const gotoProductSearch = (function () {...})
        const searchForRecord = (function () {...})
        const populateForm = (function (j) {...})

    for (let i = 0; i < recordCount; i++) {
        const j = i + 1;
        if (serialIsValid === false) {
            gotoProductSearch
                .then(searchForRecord)
                .then(populateForm(j))
                .then(findValidSerial(i))
        } else {
            console.log('serial number validity' + serialIsValid);
            expect(serialIsValid).not.toContain('Unknown');
            break;
        }
    }
    console.log('serial number validity' + serialIsValid);
}

When I've tried to chain them like that, I received this error - TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'

Have edited my code from my actual test and apologies if I've made mistakes in doing so. Would greatly appreciate comments or explanation on how to do this in general though, b/c I know I'm not doing it correctly. Thanks in advance.

Mugshep
  • 788
  • 1
  • 9
  • 18
  • What is the problem you're trying to solve? Is the loop not breaking when you find the element you want? You did a nice job explaining the scenario, but I don't see a clearly defined problem statement or goal. – Narm May 17 '19 at 16:44
  • Can you confirm about following line log the records length correctly ? console.log('records', records.length, 'recordCount', recordCount); – Nitin Sahu May 17 '19 at 18:31
  • @Narm - thanks for pointing that out. I've updated my explanation to make it clear I can't enter into my loop. – Mugshep May 20 '19 at 12:34
  • @NitinSahu - both values are 0 ('zero'), even when trying to resolve the promise (as per https://stackoverflow.com/a/34713769/4597676) – Mugshep May 20 '19 at 13:24

2 Answers2

0

I would suggest looking into async / await and migrating this test. Why migrate? Protractor 6 and moving forward will require async / await. In order to do that, you will need to have SELENIUM_PROMISE_MANAGER: false in your config and await your promises. In my answer below, I'll use async / await.

Below is my attempt to rewrite this as async / await. Also try to define your ElementFinders, numbers, and other stuff when you need them so you can define them as consts.

  it('should select a customer who has a valid serial number', async () => {
    const products = new HomePage();

    let serialIsValid = false;   // Setting the value to false is enough
                                 // and :boolean is not needed

    const recordList = element.all(by.css(`mat-list.desco-list`));
    const recordCount = await recordList.count();
    console.log(`recordCount ${recordCount}`);

    // This could be rewritten with .each
    // See https://github.com/angular/protractor/blob/master/lib/element.ts#L575
    // await recordList.each(async (el: WebElement, index: number) => {
    for (let i = 0; i < recordCount; i++) {
      if (serialIsValid === false) {
        const j = index + 1;  // Not sure what j is being used for...
        await products.btnFormsSelector.click();
        await products.formSelectorRepossession.click();
        await browser.wait(EC.visibilityOf(products.itemSearch));
        await products.itemSearch.element(by.tagName('input'))
            .sendKeys(browser.params.search_string);
        await products.itemSearch.element(by.id('btnSearch')).click();
        await browser.wait(
            EC.visibilityOf(await products.itemSearch.element(
            by.id('list-container'))));  // Maybe use a boolean check?
        const selectedElement = element(by.tagName(
            `#itemSearch mat-list:nth-child(${{j}})`));
        await selectedElement.click();

        // not sure what doStuffForm is but I'm guessing it returns a promise.
        await browser.wait(EC.visibilityOf(await products.doStuffForm));
        await browser.sleep(1000);   // I would avoid sleeps since this might
                                     // cause errors (if ran on a slower machine)
                                     // or just cause your test to run slow

        await element(by.css(
            '#successful mat-radio-button:nth-child(1) label')).click();
        await browser.sleep(1000);
        expect(await element(by.css('.itemDetailsContainer'))).toBeTruthy();
        const serialNumber = await products.productIDNumber.getText();
        if (item !== 'Unknown') {
          expect(serialNumber).not.toContain('Unknown');
          serialIsValid = true;
        }
        // The else statement if you were using i in a for loop, it is not
        // a good idea to increment it twice.
      } else {
        // So according to this, if the last item is invalid, you will not break
        // and not log this. This will not fail the test. It might be a good idea
        // to not have this in an else statement.
        console.log(`serial is valid: ${serialIsValid}`);
        expect(serialNumber).not.toContain('Unknown');
        break;
      }
    }
    console.log('serial number validity: ', serialIsValid);
  });
cnishina
  • 5,016
  • 1
  • 23
  • 40
  • Thank you for this. I've rewritten my code to try to follow suggestions, but am not able ot get it to work. Some of the errors i'm getting - `Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.`, and `(node:23064) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 4)`. Will keep trying though - thanks again! – Mugshep May 22 '19 at 15:25
0

Can you check the count again after updating your code by following snippet

element.all(by.css(`mat-list.desco-list`)).then(function(records) => {
            recordCount = records.length;
            console.log(recordCount);
        });


                             OR

There is count() function in ElementArrayFinder class which returns promise with count of locator

element.all(by.css(`mat-list.desco-list`)).then(function(records) => {
                records.count().then(number => {
                console.log(number); })
            });
Nitin Sahu
  • 611
  • 6
  • 12
  • 1
    thanks for this - i hadn't thought of passing an object into the `.then` function. The count is still zero though. Will keep looking into this. Thanks again – Mugshep May 22 '19 at 15:43
  • Of course. Have not been able to get it to work yet though. Will do if I can. – Mugshep May 29 '19 at 15:54