Lesiak
Lesiak

Reputation: 25936

cy.origin() and immediate redirect

I am having trouble with testing my oauth-secured application. The problem manifests itself when there is no public page - user is immediately redirected to OAuth server it they are not authenticated.

I managed to reproduce the problem in much simpler setup:

Here are respective apps (in Flask):

Fake app

from flask import Flask, redirect, render_template_string

app = Flask(__name__)

app_host="fake-app"
app_port=5000
app_uri=f"http://{app_host}:{app_port}"
oauth_host="fake-oauth-server"
oauth_port=5001
oauth_uri=f"http://{oauth_host}:{oauth_port}"

@app.route('/')
def hello():
    return render_template_string('''<!doctype html>
           <html>
               <body>
                   <p>Hello, World MainApp!</p>
                   <a id="loginButton" href="{{ oauth_uri }}?redirect_uri={{ app_uri }}">Login</a>
               </body>
           </html>
           ''',
           oauth_uri=oauth_uri,
           app_uri=app_uri
    )

@app.route('/goto-oauth')
def goto_oauth():
    return redirect(f"{oauth_uri}?redirect_uri={app_uri}")

if __name__ == '__main__':
    app.run(host=app_host, port=app_port)

Fake oauth server:

from flask import Flask, render_template_string, request

app = Flask(__name__)

oauth_host="fake-oauth-server"
oauth_port=5001

@app.route('/')
def login():
    return render_template_string(
    '''<!doctype html>
      <html>
          <body>
              <p>Please log in</p>
              <label>Username: <label><input id="username" />
              <label>Password: <label><input id="password" />
              <a id="submit-password" href="{{ redirect_uri }}">Submit</a>
          </body>
      </html>
      ''', redirect_uri=request.args.get('redirect_uri'))


if __name__ == '__main__':
    app.run(host=oauth_host, port=oauth_port)

First flow: there is a publicly available page with Login button

This is possible to test with cy.origin:

describe('My Scenarios', () => {
  beforeEach(() => {
    cy.visit('/');
    cy.contains('MainApp');
    cy.get('a#loginButton').click();
    cy.origin('http://fake-oauth-server:5001', () => {
      cy.contains('Please log in');
      cy.get('input#username').type('user1');
      cy.get('input#password').type('password1');
      cy.get('a#submit-password').click()
    });
  });

  it.only('test flask', () => {
    cy.visit('/');
    cy.contains('MainApp');
  });
});

Problematic flow: immediate redirect to Oauth server

describe('My Scenarios', () => {
  beforeEach(() => {
    cy.visit('/goto-oauth');

    cy.origin('http://fake-oauth-server:5001', () => {
      cy.contains('Please log in');
      cy.get('input#username').type('user1');
      cy.get('input#password').type('password1');
      cy.get('a#submit-password').click()
    });
  });

  it.only('test flask', () => {
    cy.visit('/');
    cy.contains('MainApp');
  });
});

Fails with:

CypressError: `cy.origin()` requires the first argument to be a different domain than top. You passed `http://fake-oauth-server:5001` to the origin command, while top is at `http://fake-oauth-server:5001`.

Either the intended page was not visited prior to running the cy.origin block or the cy.origin block may not be needed at all.

There is no publicly available page in my app - how can I amend the test to make it work?

Upvotes: 4

Views: 4995

Answers (2)

Yuriy Marchenko
Yuriy Marchenko

Reputation: 1

It's a dirty hack, but it works for me...
Just add an extra visit to a 404 page (or any page that doesn't require logging in, most likely you have one) so that Cypress gets the origin right.

    describe('My Scenarios', () => {
      beforeEach(() => {
        cy.visit('/404', { failOnStatusCode: false });
        cy.visit('/goto-oauth');
    
        cy.origin('http://fake-oauth-server:5001', () => {
          cy.contains('Please log in');
          cy.get('input#username').type('user1');
          cy.get('input#password').type('password1');
          cy.get('a#submit-password').click()
        });
      });
    
      it.only('test flask', () => {
        cy.visit('/');
        cy.contains('MainApp');
      });
    });

Upvotes: 0

Fody
Fody

Reputation: 31862

It seems to work if visit the redirect URL inside the cy.origin().

I set the app on http://localhost:6001 and the auth server on http://localhost:6003, using express rather than flask.

Test

describe('My Scenarios', () => {
  beforeEach(() => {
    cy.origin('http://localhost:6003', () => {
      cy.visit('http://localhost:6001/goto-oauth')
      cy.contains('Please log in');
      cy.get('input#username').type('user1');
      cy.get('input#password').type('password1');
      cy.get('a#submit-password').click()
    });
  });

  it('test main app', () => {
    cy.visit('http://localhost:6001')
    cy.contains('MainApp')
  })
})

App

const express = require('express')
function makeApp() {
  const app = express()
  app.get('/', function (req, res) {
    res.send(`
      <html>
      <body>
        <p>Hello, World MainApp!</p>
        <a id="loginButton" href="http://localhost:6003?redirect_uri=http://localhost:6001">
          Login
        </a>
      </body>
      
      </html>
    `)
  })
  app.get('/goto-oauth', function (req, res) {
    res.redirect('http://localhost:6003')
  })

  const port = 6001

  return new Promise((resolve) => {
    const server = app.listen(port, function () {
      const port = server.address().port
      console.log('Example app listening at port %d', port)

      // close the server
      const close = () => {
        return new Promise((resolve) => {
          console.log('closing server')
          server.close(resolve)
        })
      }

      resolve({ server, port, close })
    })
  })
}

module.exports = makeApp

Auth

const express = require('express')
function makeServer() {
  const app = express()
  app.get('/', function (req, res) {
    res.send(`
    <!doctype html>
    <html>
        <body>
            <p>Please log in</p>
            <label>Username: <label><input id="username" />
            <label>Password: <label><input id="password" />
            <a id="submit-password" href="http://localhost:6001">Submit</a>
        </body>
    </html>
    `)
  })

  const port = 6003

  return new Promise((resolve) => {
    const server = app.listen(port, function () {
      const port = server.address().port
      console.log('Example app listening at port %d', port)

      // close the server
      const close = () => {
        return new Promise((resolve) => {
          console.log('closing server')
          server.close(resolve)
        })
      }

      resolve({ server, port, close })
    })
  })
}

module.exports = makeServer

Upvotes: 5

Related Questions