Testcafe: LocalStorage disappears after each test (0.19-alpha)

Created on 20 Feb 2018  路  16Comments  路  Source: DevExpress/testcafe

Are you requesting a feature or reporting a bug?

Reporting a bug

What is the current behavior?

When running a new test (in the same fixture or different fixture), the localStorage will get destroyed without leaving behind the data.

My localStorage has multiple items within and once the test occurs, all the items are gone. I have looked up issues but they seem to involve preserveURL. Even if I give my role preserveURL, the localStorage seems to disappear after each test.

If I iterate through each item within the localStorage, I can see it exists at the end of my first test, then at the second test it will have disappeared.

What is the expected behavior?

LocalStorage should be persisted throughout the entire test suite with the same role.

How would you reproduce the current behavior (if this is a bug)?

I have provided some test code and a URL to show the bug behaviour.

Provide the test code and the tested page URL (if applicable)

Tested page URL: https://da.viddiaz.com/tmp/testcafe.html

Test code

import {Role, Selector} from "testcafe";

const myrole = Role("https://da.viddiaz.com/tmp/testcafe.html", async t => {
    console.log("hi");
});

fixture("Test local storage").page("https://da.viddiaz.com/tmp/testcafe.html");

test("Test adding to local storage", async t => {
    await t
        .useRole(myrole)
        .expect(Selector("#items").textContent).eql("0")
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .expect(Selector("#items").textContent).eql("4");
});

test("Test local storage is saved", async t => {
    await t
        .useRole(myrole)
        .expect(Selector("#items").textContent).eql("4")
        .debug();
});

Specify your

  • operating system: Fedora 26
  • testcafe version: 0.19-alpha (this also occurs on 0.18.6)
  • node.js version: v8.9.4
Auto-locked bug

Most helpful comment

In this case, you don't need roles. If you need to modify localStorage from multiple tests, you need to write custom code for these purposes.

Use CustomFunctions for saving and restoring localStorage on afterEach and beforeEach hooks.

See the example below:

import { Selector, ClientFunction} from "testcafe";

const loadLocalStorageFromGlobalStorage = ClientFunction((storage) => {
    Object.keys(storage).forEach(key => {
        window.localStorage.setItem(key, storage[key])
    });
    updateItemsCount();
});

const getActualLocalStorageStage = ClientFunction(() => {
    var storage = { };
    Object.keys(localStorage).forEach(key => {
        var value = localStorage.getItem(key);
        if(value && value.indexOf('bla') > -1)
            storage[key] = localStorage.getItem(key);
    });
    return storage;
});

let sharedLocalStorage = { };

fixture("Test local storage")
    .page("http://da.viddiaz.com/tmp/testcafe.html")

    .beforeEach( async t => {
        await loadLocalStorageFromGlobalStorage(sharedLocalStorage);
    })
    .afterEach( async t => {
        sharedLocalStorage = await getActualLocalStorageStage();
    });

test("Test 1", async t => {
    await t
        .expect(Selector("#items").textContent).eql("0")
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .expect(Selector("#items").textContent).eql("4");
});

test("Test 2", async t => {
    await t
        .expect(Selector("#items").textContent).eql("4")
        .click(Selector("a"))
        .click(Selector("a"))
});

test("Test 3", async t => {
    await t
        .expect(Selector("#items").textContent).eql("6")
});

Before each test, we load data from global sharedLocalStorage. And after each test, we save it to sharedLocalStorage.

Note that the loadLocalStorageFromGlobalStorage function will be executed after your test page is loaded so you need to call updateItemsCount function to update your html.

So add this code onto your page:

function updateItemsCount () {
    document.querySelector("#items").innerHTML = localStorage.length;
}

Please also note that if you write tests that depend on each other, there is a risk that a lot of tests will fail even if only one test fails.

All 16 comments

Hi

If you want to use roles mechanism to keep data between tests, you need to call localStorage functions from Role logic. I've modified your example to demonstrate this approach:

import {Role, Selector} from "testcafe";

fixture("Test local storage")

const myrole = Role("http://da.viddiaz.com/tmp/testcafe.html", async t => {
    await t
        .expect(Selector("#items").textContent).eql("0")
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .expect(Selector("#items").textContent).eql("4");
}, { preserveUrl : true });

test("Test adding to local storage", async t => {
    await t.useRole(myrole);
});

test("Test local storage is saved", async t => {
    await t.useRole(myrole);
    await t.expect(Selector("#items").textContent).eql("4");
});

Here I'm using the preserveUrl option to start a new test from Role's url.

I need to mention that I faced an issue when not using the preserveUrl option. We need time to research it. But since you use preserveUrl you should not have any problems.

Please also note that the roles mechanism was designed for authentication purposes. If you use it for a different goal, you may face architectural limitations.

Hi Alex,

I'm not too sure I understand - if I do this approach, how would you build upon tests (e.g first test does one action which saves into localStorage, second and third test modifies the data stored within the localStorage and the fouth test removes the data from the localStorage)? If you can only do things within the Role constructor then how are you supposed to perform multiple tests with the expected data?

If I something like this:

import {Role, Selector} from "testcafe";

fixture("Test local storage")

const myrole = Role("http://da.viddiaz.com/tmp/testcafe.html", async t => {
    await t
        .expect(Selector("#items").textContent).eql("0")
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .expect(Selector("#items").textContent).eql("4");
}, { preserveUrl : true });

test("Test adding to local storage", async t => {
    await t.useRole(myrole)
        .expect(Selector("#items").textContent).eql("4")
        .click(Selector("a"))
        .expect(Selector("#items").textContent).eql("5");
});

test("Test local storage is saved", async t => {
    await t.useRole(myrole);
    await t.expect(Selector("#items").textContent).eql("5");
});

It still doesn't work.

Cheers

In this case, you don't need roles. If you need to modify localStorage from multiple tests, you need to write custom code for these purposes.

Use CustomFunctions for saving and restoring localStorage on afterEach and beforeEach hooks.

See the example below:

import { Selector, ClientFunction} from "testcafe";

const loadLocalStorageFromGlobalStorage = ClientFunction((storage) => {
    Object.keys(storage).forEach(key => {
        window.localStorage.setItem(key, storage[key])
    });
    updateItemsCount();
});

const getActualLocalStorageStage = ClientFunction(() => {
    var storage = { };
    Object.keys(localStorage).forEach(key => {
        var value = localStorage.getItem(key);
        if(value && value.indexOf('bla') > -1)
            storage[key] = localStorage.getItem(key);
    });
    return storage;
});

let sharedLocalStorage = { };

fixture("Test local storage")
    .page("http://da.viddiaz.com/tmp/testcafe.html")

    .beforeEach( async t => {
        await loadLocalStorageFromGlobalStorage(sharedLocalStorage);
    })
    .afterEach( async t => {
        sharedLocalStorage = await getActualLocalStorageStage();
    });

test("Test 1", async t => {
    await t
        .expect(Selector("#items").textContent).eql("0")
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .click(Selector("a"))
        .expect(Selector("#items").textContent).eql("4");
});

test("Test 2", async t => {
    await t
        .expect(Selector("#items").textContent).eql("4")
        .click(Selector("a"))
        .click(Selector("a"))
});

test("Test 3", async t => {
    await t
        .expect(Selector("#items").textContent).eql("6")
});

Before each test, we load data from global sharedLocalStorage. And after each test, we save it to sharedLocalStorage.

Note that the loadLocalStorageFromGlobalStorage function will be executed after your test page is loaded so you need to call updateItemsCount function to update your html.

So add this code onto your page:

function updateItemsCount () {
    document.querySelector("#items").innerHTML = localStorage.length;
}

Please also note that if you write tests that depend on each other, there is a risk that a lot of tests will fail even if only one test fails.

Thank you so much! That fixed the issue I was having (although I'll keep the bug open because of the other issue that was found)

Cheers :)

You are welcome =)

@AlexKamaev
I have the same issue with the useRole even though I've used preserve url attribute. User credentials are not saved for the second test.

@TestAndunR
Hi, would you please clarify what testcafe version you are using?
We had a similar issue, which is now fixed: https://github.com/DevExpress/testcafe/issues/2282. The fix is available starting from v0.19.2.
If your version is higher, could you please provide the page and the test file for me to investigate it?

Hi @AlexKamaev,
I'm using the v0.19.2. Following is the web page
https://sigma.slappforge.com
And I've attach the files

page-object.txt
test.txt

Thanks for the details. I'll investigate it

Thank you very much 馃檪

@TestAndunR I've researched the issue. It appeared not to be connected with the original issue so I've created a separate ticket https://github.com/DevExpress/testcafe/issues/2475.

is there a way to do updateItemCount() in some other way than adding it to the page?
function updateItemsCount () {
document.querySelector("#items").innerHTML = localStorage.length;
}

The reason is that I don麓t sit with dev team so I don麓t have the possibility or mandate to modify the application.

I recommended to add the updateItemsCount method to the page only to prevent code duplicating. In that example, we need to use the code document.querySelector("#items").innerHTML = localStorage.length twice: on page load and on local storage restoring. You can modify the example in the following manner:

const loadLocalStorageFromGlobalStorage = ClientFunction((storage) => {
    Object.keys(storage).forEach(key => {
        window.localStorage.setItem(key, storage[key])
    });
    document.querySelector("#items").innerHTML = localStorage.length;
});

After localStorage is restored you are free to write any JS code inside a ClientFunction.

I tried this myself but I got this error then

  • Error in fixture.beforeEach hook -
    An error occurred in ClientFunction code:
    TypeError: Cannot set property 'innerHTML' of null

Perhaps you've made a mistake in your selector. This error is of pure JS nature and is not connected to TestCafe

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs or feature requests. For TestCafe API, usage and configuration inquiries, we recommend asking them on StackOverflow.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

marchugon picture marchugon  路  4Comments

jvanoostveen picture jvanoostveen  路  4Comments

AndreyBelym picture AndreyBelym  路  3Comments

Lukas-Kullmann picture Lukas-Kullmann  路  3Comments

darkowic picture darkowic  路  3Comments