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

Ability to disable javascript for the url under test #1611

Open
davemcorwin opened this issue Apr 18, 2018 · 26 comments
Open

Ability to disable javascript for the url under test #1611

davemcorwin opened this issue Apr 18, 2018 · 26 comments
Labels
E2E Issue related to end-to-end testing stage: ready for work The issue is reproducible and in scope type: feature New feature that does not currently exist

Comments

@davemcorwin
Copy link

I would like to be able to use Cypress to test an application when Javascript is disabled.

Current behavior:

Cypress makes itself appear to run from the same domain as the application under test and Chrome allows Javascript to be disable by domain only, so Javascript cannot be disabled without affecting Cypress itself.

Desired behavior:

The ability to disable javascript for the url under test, possibly with:

  • A Cypress option to disable JS for the url under test (not sure this is possible)
  • A Cypress option to NOT run from the same domain as the url under test (this would come with limitations)
@jennifer-shehane jennifer-shehane added the stage: proposal 💡 No work has been done of this issue label Apr 18, 2018
@kuceb
Copy link
Contributor

kuceb commented Apr 18, 2018

This is very possible, seems like all that needs to done is strip all the script tags and inline js and make sure <noscript> tags get rendered

@bahmutov
Copy link
Contributor

well, we can strip all JavaScript tags and inline handlers, but this does not make noscript tags visible. To truly do this we would need to embed the application iframe with sandbox attribute, which would disable the injection script we use to set the document domain to localhost. Hmm.

@kuceb
Copy link
Contributor

kuceb commented Apr 24, 2018

Could we not simply strip <noscript> and </noscript> , but leave everything inside the tags there?

@bahmutov
Copy link
Contributor

bahmutov commented Apr 24, 2018 via email

@maxcbc
Copy link

maxcbc commented Jun 18, 2018

+1 to this, (apologies for commenting but not contributing). I just wanted to say this would be really useful for sites which have to support browsers with javascript disabled. It often gets missed in automated tests.

@kuceb
Copy link
Contributor

kuceb commented Jun 18, 2018

@maxcbc if you 👍 the top level comment, that will help us sort issues by community interest

@rafael-anachoreta
Copy link

rafael-anachoreta commented Apr 5, 2019

My use case is slightly different - I want to check how the page behaves before JS runs to see if it matches our SEO needs.

Is there any known workaround to achieving that?

@jennifer-shehane jennifer-shehane added the type: feature New feature that does not currently exist label Apr 24, 2019
@jbpallingayan
Copy link

Any update on this? my case is javascript will call html output to the page when i turn off javascript the html will load.

@cypress-bot cypress-bot bot added stage: ready for work The issue is reproducible and in scope and removed stage: proposal 💡 No work has been done of this issue labels May 20, 2019
@melibe23
Copy link

I would really appreciate this feature for SEO purposes too.

@joshuapaling
Copy link

Likewise, I want it to test if my react server side rendering is working. For now I'm going to settle with making an http request and grepping the resulting text or something - but it's far from ideal.

@kuceb
Copy link
Contributor

kuceb commented Feb 12, 2020

someone could try loading a no-script extension and match it to only run on the aut-iframe url. Not sure if that would work.

@Vadorequest
Copy link

Vadorequest commented Apr 3, 2020

I'd love the ability to do this. I've recently attempted to migrate a project dependencies and 2 of them broke Next.js SSR support. A way for automating such tests would be great.

UnlyEd/next-right-now#27

Because I didn't automatically detect this SSR regression, I updated multiple other packages before understanding SSR was broken. Then, I had to replay the online preview of each manually, by disabling JS in the browser.

At some point, I figured out that the issue was coming from next-with-apollo dependency (MAJOR version update) (see lfades/next-with-apollo#126) and tried to rollback this particular dep to its previous version, which, surprisingly, didn't work out because another dependency that I also had updated was causing the same regression (react-apollo (PATCH version update), see apollographql/react-apollo#3902)

Eventually, I had to rewrite my git history to get back in time and replay all commits one by one until I figured this whole plot out. Which took me some 3-5h of investigation.

All this to say that I'd love a way to automate this, and ensure some other PATCH dependency update doesn't break some feature again in the future.

And, I got lucky here, because I had been meticulous and deployed an online version after each package update (gave me ability to test afterwards and find origin of regression). Otherwise it could (would) have taken me days to figure this out.

@pke
Copy link

pke commented Apr 30, 2020

So is this in theory possible to implement? If so where would I start for creating a PR?

@husseinbob
Copy link

I've been using @bahmutov 's handy guide here: https://glebbahmutov.com/blog/ssr-e2e/

Issue is if I have two such tests, the second write(html) just appends to the first, so now I have two of each element, and hence my selectors break.

@pke
Copy link

pke commented May 5, 2020

This guide is nice and helps a bit. It still does not reveal the noscript tags content though :(

@pke
Copy link

pke commented May 5, 2020

I am trying

cy.state("window").parent.document.querySelector(".iframes-container iframe").sandbox = "";

but it seems the iframe is already sandboxed but it does not show the noscript tags.

@pke
Copy link

pke commented May 5, 2020

Update:

This seems to be almost done:

describe("noscript", function () {
  it("should not show the Get Codes button", function() {
    cy.request("/")
      .its("body")
      .then(html => {
        debugger
        const parentDocument = cy.state("window").parent.document
        const iframe = parentDocument.createElement("iframe")
        const oldIframe = parentDocument.querySelector(".iframes-container iframe")
        iframe.id = oldIframe.id
        iframe.className = oldIframe.className
        iframe.sandbox.add("allow-forms")
        iframe.srcdoc = html
        oldIframe.parentNode.replaceChild(iframe, oldIframe)
        //cy.state("document").write(html)
      })
    cy.get('#encrypt').should('be.disabled')
  })
});

It shows all the <noscript> tags, the CSS is working!
image

But the cy.get('#encrypt').should('be.disabled') fails now with Timed out retrying: Expected to find element: #encrypt, but never found it.

I guess that's because the cy.get method has no access to the newly created iframe.

Any ideas @bahmutov?

@pke
Copy link

pke commented May 5, 2020

One step more: With chromeWebSecurity disabled in the cypress.json it works like this:

describe("noscript", function () {
  it("should not show the Get Codes button", function() {
      const parentDocument = cy.state("window").parent.document
      const iframe = parentDocument.querySelector(".iframes-container iframe")
      iframe.sandbox.add("allow-forms")
      iframe.src = "http://htpc:4000/"
      cy.get('#encrypt').should('be.disabled')
  })
});

image

It was complaining about Cross-Site access, which does not make much sense since the iframe uses the same host like the spec.

image

@pke
Copy link

pke commented May 5, 2020

This could be neatly packaged into extending the visit command using a new, non-breaking script option, which is by default true like this:

Cypress.Commands.overwrite('visit', (orig, url, options = {}) => {
  const parentDocument = cy.state("window").parent.document
  const iframe = parentDocument.querySelector(".iframes-container iframe")
  if (false === options.script) {
    if (false !== Cypress.config("chromeWebSecurity")) {
      throw new TypeError("When you disable script you also have to set 'chromeWebSecurity' in your config to 'false'")
    }
    iframe.sandbox = ""
  } else {
    // In case it was added by a visit before, the attribute has to be removed from the iframe
    iframe.removeAttribute("sandbox")
  }
  return orig(url, options);
})

And the spec gets very clean then:

describe("noscript", function () {
  it("should not show the Get Codes button", function() {
      cy.visit("/", { script: false })
      cy.contains("The password is encrypted locally and for that you need to enable JavaScript.").should("exist")
      cy.get('button')
        .first()
        .should('be.disabled')
        .contains("Please enable JavaScript")
  })
});

@pke
Copy link

pke commented May 25, 2020

Any thoughts on my solution @bahmutov?

@jennifer-shehane
Copy link
Member

@pke The workaround does appear to work. This workaround would only work in Chrome currently.

cypress.json

{
  "chromeWebSecurity": false
}

spec.js

Cypress.Commands.overwrite('visit', (orig, url, options = {}) => {
  const parentDocument = cy.state("window").parent.document
  const iframe = parentDocument.querySelector(".iframes-container iframe")
  if (false === options.script) {
    if (false !== Cypress.config("chromeWebSecurity")) {
      throw new TypeError("When you disable script you also have to set 'chromeWebSecurity' in your config to 'false'")
    }
    iframe.sandbox = ""
  } else {
    // In case it was added by a visit before, the attribute has to be removed from the iframe
    iframe.removeAttribute("sandbox")
  }
  return orig(url, options);
})

it('test', () => {
  cy.visit('index.html', { script: false })
  cy.get('h1').contains('JavaScript is disabled')
})

index.html

<html>
<body>
  Hello, world
  <noscript>
    <h1>JavaScript is disabled</h1>
  </noscript>
</body>
</html>

@eric-burel
Copy link
Contributor

eric-burel commented Nov 8, 2021

Hi, I still have trouble with this issue, when running multiple tests that needs JS to be disabled.
This is my command:

Cypress.Commands.add("visitAsHtml", (route: string) => {
  cy.request(route)
    .its("body")
    .then((html) => {
      // remove the application code JS bundle
      html = html.replace(
        /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
        ""
      );
      cy.document().invoke({ log: false }, "write", html);
    });
  // now we can use "normal" Cypress api on the page
});

The problem is that if you do this:

cy.visitAsHtml("/fr/foobar")
cy.get("html")
        .should("have.attr", "lang", "fr")
cy.visitAsHtml("/en/foobar")
cy.get("html")
        .should("have.attr", "lang", "en")

The second assertion will fail, you still have the page in French while you should not. This is a false positive, the HTML response is correct, but cy.get("html") always return the first HTML written, it does not update.

Even if you create 2 tests, it still doesn't work.

@KhimairaCrypto
Copy link

chromeWebSecurity
Any update regarding a browser independent solution on CY 10?

@distante
Copy link

distante commented Feb 8, 2023

This is an important feature to do e2e test of things Angular Universal.

@JeyDotC
Copy link

JeyDotC commented May 11, 2023

Hi, I still have trouble with this issue, when running multiple tests that needs JS to be disabled. This is my command:

Cypress.Commands.add("visitAsHtml", (route: string) => {
  cy.request(route)
    .its("body")
    .then((html) => {
      // remove the application code JS bundle
      html = html.replace(
        /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
        ""
      );
      cy.document().invoke({ log: false }, "write", html);
    });
  // now we can use "normal" Cypress api on the page
});

...

Hi @eric-burel , I know it's been a while since you added your comment, (I'm new to cypress), I found your solution quite useful!

To fix your problem with the visitAsHtml command, you need to have into account that document.write (at least in this cypress environment) appends to the document (not overrides). So, in order to make the command override the entire document all you need to do is this:

Cypress.Commands.add("visitAsHtml", (route: string) => {
  cy.request(route)
    .its("body")
    .then((html) => {
      // remove the application code JS bundle
      html = html.replace(
        /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
        ""
      );
      cy.document().invoke({ log: false }, "open"); // <-- Add This
      cy.document().invoke({ log: false }, "write", html);
      cy.document().invoke({ log: false }, "close"); // <-- And this
    });
});

The above code allows to override the entire document, instead of appending if it already existed within the same test.

Hope this helps you and anyone looking at this thread.

@nagash77
Copy link
Contributor

Hi @JeyDotC , check out our community chat, it can be helpful for debugging or answering questions on how to use Cypress.

@nagash77 nagash77 added the E2E Issue related to end-to-end testing label May 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E2E Issue related to end-to-end testing stage: ready for work The issue is reproducible and in scope type: feature New feature that does not currently exist
Projects
None yet
Development

No branches or pull requests