0

I have a case where I would like to use invoke the t test handler with the t.ctx context available outside of the test() function. Is this possible at all? From what I see when I run this script I do see the console spitting out all the available handler options to me, but trying to create an empty user form t.ctx.userForm = {} throws the error:

Cannot implicitly resolve the test run in the context of which the test controller action should be executed. Use test function's 't' argument instead.

import { t } from 'Testcafe';

const setFormContext = (t) => {
    console.log(t);
    t.ctx.userForm = {};
};

export const intializeEmptyForm = (async(t) => {
    await setFormContext(t)
})(t);

I basically want to be able to have code like such, but without overcbloating the POM AccountPage object with custom functions not related to what's on the page, or relying on something like firstName to be invoked in order to make the t.ctx.userForm available.

export const AccountPage = {

    enterFirstName: async (firstName) => {
        let firstNameField = Selector('#firstName');
        await t.typeText(firstNameField, firstName);
        // t.ctx.userForm = {}; <- ideally not here as it's tied to enterFirstName
        t.ctx.userForm.firstName = firstName;
    },

    enterLastName: async (lastName) => {
        let lastNameField = Selector('#lastName');
        await t.typeText(lastNameField, lastName);
        t.ctx.userForm.lastName = lastName;
    }

    // ... some logic that maps t.ctx.userForm values to an assertion that checks all form values after clicking 'Save' are actually present.
}
import { AccountPage } from 'AccountPage';

...

test('User form successfully saves and updates correctly', async () => {
    await AccountPage.enterFirstName('First');
    await AccountPage.enterLastName('Last');
    await AccountPage.clickSave()
})
Alex Skorkin
  • 4,264
  • 3
  • 25
  • 47
Alex
  • 1

2 Answers2

1

The import {t} from 'testcafe' statement looks for a test(), beforeEach(), afterEach() or other test function in the call stack and gets the t instance from its arguments. This error occurs when an imported t is used in a function that is not called from a test or hook. This is what happens in your case, since the arrow function whose promise is exported in initializeEmptyForm is self-invoked.

As a solution, you can export a function in initializeEmptyForm (not a promise) and call it from test context.

helper.js

import { t } from 'testcafe';

export const initializeEmptyForm = async () => {
    await setFormContext(t);
};

test.js

import { initializeEmptyForm } from './helper.js';

fixture 'fixture 1'
    .beforeEach(async t => {
        await initializeEmptyForm();
    });

test('test 1', async t => {
    // ...
});

Alternatively, you can export a function that takes t as an argument:

helper.js

export const initializeEmptyForm = async t => {
    await setFormContext(t);
};

test.js

import { initializeEmptyForm } from './helper.js';

fixture 'fixture 1'
    .beforeEach(async t => {
        await initializeEmptyForm(t);
    });

test('test 1', async t => {
    // ...
});
0

My thoughts on this are when visiting a form with a click action then it may be cleaner to do so as part of the click action.

import { Selector, t } from 'testcafe';

export const AccountPage = {

  clickEditForm: async () => {
     let editButton = Selector('button').withText('Edit');
     await t.click(editButton);
    
     // guarantees on a click form we have setup the userForm object;
     t.ctx.userForm = {};
  }, 

  enterFirstName: async (firstName) => {
     let firstNameField = Selector('#firstName');
     await t.typeText(firstNameField, firstName);

     t.ctx.userForm.firstName = firstName;
  },

  enterLastName: async (lastName) => {
     let lastNameField = Selector('#lastName');
     await t.typeText(lastNameField, lastName);

     t.ctx.userForm.lastName = lastName;
  }

  // map t.ctx.userForm values to assertions that checks all form values after clicking 'Save'.
  verifyAccountFormDetails: async(expectFormValue = []) => {
    // Grab form values
    // Then map them to parameters desired or something.
  }

}

This allows us to then pass the values around in a cleaner manner with the POM.

import { AccountPage } from 'AccountPage';

...

test('User form successfully saves and updates correctly', async () => {
  await AccountPage.enterFirstName('First');
  await AccountPage.enterLastName('Last');
  await AccountPage.clickSave();
  ...

  // After doing something like account form save then verify values 
  persist based on what you want to check
  await AccountPage.verifyAccountFormDetails(['firstName', 'email'])
})
Alex
  • 1