Meteor Velocity Jasmine: Prevent beforeEach Global Scope Across Describes - meteor

In my Meteor app, I have a form at /join with a disabled button. I test this disabled state with the following integration test file:
// tests/jasmine/client/integration/user/joinSpec.js.coffee
describe 'user', ->
describe 'join', ->
beforeEach ->
Router.go 'join_path'
it 'is unsubmittable by default', ->
expect($('#join-submit')).toHaveAttr 'disabled', 'disabled'
I also have a form at /signup with a disabled button. I test that disabled state with this additional integration test file:
// tests/jasmine/client/integration/user/signupSpec.js.coffee
describe 'user', ->
describe 'signup', ->
beforeEach ->
Router.go 'signup_path'
it 'is unsubmittable by default', ->
expect($('#signup-submit')).toHaveAttr 'disabled', 'disabled'
Both tests pass independently of each other (i.e. when only one file exists). However, the user.signup test only passes in absense of the user.join test, I assume due to the way Meteor executes the files in order by filename.
It seems like beforeEach has global scope, and the one from user.join is overriding the one in user.signup, causing the sign up test to execute on the wrong route and fail. Any idea why this is, or how to lock it down to local scope?
Thanks!

The problem was Iron Router. Refer to the following article for resolution: https://meteor-testing.readme.io/docs/jasmine-integration-tests-with-iron-router

Related

Cypress getting odd duplicate cookies set

I'm setting up a Cypress E2E test, with a basic first step of loading the app and signing in. When cypress loads the page, however, I see odd behavior in that the cookies are being set twice, kindof, like:
xsrftoken - someValue123 - env.app.domain.com // hostOnly: true
xsrftoken - otherValue12 - .env.app.domain.com // hostOnly: false
sessiontoken - someValue - env.app.domain.com // hostOnly: true
sessiontoken - someValue - .env.app.domain.com // hostOnly: false
sessiontoken value stays consistent if I use cy.session (see code below), otherwise it also differs between the two. The only difference besides the leading . is the hostOnly value.
This appears to cause problems because (from what I can tell) on subsequent requests the wrong xsrftoken gets sent.
When I visit the app outside of Cypress, the cookies are only set once:
xsrftoken - someValue123 - env.app.domain.com
sessiontoken - someValue - env.app.domain.com
The Cypress setup is pretty basic:
// With this setup, the sessiontoken doesn't change, but the xsrftoken does,
// and the "Signs in" test doesn't get an authenticated session.
beforeEach(() => {
cy.session('mySession', () => {
cy.visit('https://env.app.domain.com/')
cy.get('input[type="text"]').type('userName');
cy.get('input[type="password"]').type('passWord');
cy.get('button[type="submit"').click();
})
})
it('Signs in', () => {
cy.visit('https://env.app.domain.com/')
})
// With just this single setup I can sign in successfully if I clear initial cookies:
it('Signs in', () => {
cy.visit('https://env.app.domain.com/')
cy.get('input[type="text"]').type('userName');
cy.get('input[type="password"]').type('passWord');
cy.clearAllCookies(); // <-- Should not need this...
cy.get('button[type="submit"').click();
})
I don't really think this is a Cypress issue, but maybe I'm wrong? I believe the server is some kind of nginx - I'm not certain of the exact configuration BUT I don't see this same behavior in other environments for the same app I do see this in other environments, although initially I thought I did not, at least for a few test runs.
I'm looking for either a workaround (prevent certain cookies being set?) or a root cause.
I think it might be a Cypress issue since I also have it with csrf cookies: it creates 2: one regular, second with . in
As I cannot clear all cookies in my circumstances, I have to clear them individually
cy.clearCookie('_csrf', {domain: 'domain'});
cy.clearCookie('_csrf', {domain: '.domain'});
Update: there is an existing issue on github: https://github.com/cypress-io/cypress/issues/25174

How to run Cypress BDD Feature using TAGS in the Terminal without closing the test/browser for each Feature

I have a few feature files in my project and I need to execute only the specific cucumber tags (#Regression) from the feature file using Terminal. I could able to run the feature file using the tags. But the test/Browser window gets closed and open for each feature file. In this case, I have to write a login script in all the feature files to avoid this problem.
Expectation: Test/Browser should not be closed each time and Login should happen only at the beginning of the script execution.
Can someone help me to overcome this problem?
Explanation
That you have to run the login for each Scenario in your Feature respectively is the expected behavior, since each test should be as independent as possible in itself.
In order not to have to add a login step for each Scenario again and again, there are so-called Backgrounds in Cucumber. Backgrounds describe steps that apply as a precondition for all Scenarios in a Feature.
Backgrounds behave like normal Scenarios, so for example you can create a Background in each of your Features with a Given step for the login so that it is automatically executed before each scenario.
Example
Each Feature would receive the following Background, which is then automatically executed once before each Scenario:
#SomeTag
Feature: Some Feature
Background: User is logged in
Given the user is logged in
Scenario: Some first scenario
Given ...
When ...
Then ...
Scenario: Some second scenario
Given ...
When ...
Then ...
The implementation of the step definition is then the same as for steps for your normal Scenarios and can be reused in all Features:
import { defineStep, Given } from 'cypress-cucumber-preprocessor/steps';
Given('the user is logged in', () => {
// logic for login
});
// or more generic using defineStep
defineStep('the user is logged in', () => {
// logic for login
});
Regarding the logic for the login it is often suitable to use Cypress Custom Commands (Example Login for Azure AD)

Meteor Vue Tracker - Unable to Watch Subready. Computed property with subready is not working either

I am trying to move my project from blaze to vue.
I am using meteor 1.6.1 and vue-meteor-tracker.
I had a case where in a component a subscription should take place only when the other subscription is ready.
I wrote the following code and faced the problems mentioned in the comments.
export default
data: ->
filter: null
computed:
subscriptionReady: ->
# this gets called only once and the value does not change even when $subReady.users changes.
#$subReady.users
meteor:
$subscribe:
filter: []
filter: -> Filter.findOne()
watch
'$subReady.filter': (value) ->
console.log('this watch is not working')
if value
#$subscribe('users', #filter)
'$subReady': (value) ->
console.log('this watch is not working either')
'filter': (value) ->
# this will work but i can not use this as I need the below subscription to run even when there is no filter in the db.
if value
#$subscribe('users', #filter)
I can do null handling in publication but in the above case when filter is not present, there is a default value for filter.The problem is when the filter subscription is not ready users data is returned with default filter value. The users subscription gets called again when the filter subscription is ready.
Note: The above case is not exactly the same as I am facing in the project so I apologize if I missed something and the question is not clear.
Any suggestion or help would be appreciated. Thanks in advance.

Iron router data findOne returning undefined

I have a streams publication and subscription setup but for some reason if I do the following in my route the view gets no data:
Router.route '/seasons/:season/episodes/:episode',
name: 'episode'
action: ->
#render(
'episode',
data: ->
Streams.findOne({season: #params.season, episode: #params.episode})
)
If I log the params they are there as expected, and doing a findOne manually either via the db or the browser console returns the data as expected.
If I remove the params so it just does Streams.findOne() the data returns the first stream from the database and the view has access to it as expected. I'm really not sure what's going on here.
You probably need to wait on your streams publication before trying to access the data : Pub/Sub mechanism in Meteor is asynchronous, when you subscribe to some data, you don't instantly get it back in the browser because of the underlying client/server latency.
Try reorganizing your code as follow :
Router.route '/seasons/:season/episodes/:episode',
name: 'episode'
template: 'episode'
data: ->
Streams.findOne({season: #params.season, episode: #params.episode})
waitOn: ->
Meteor.subscribe 'streams', #params.season, #params.episode

Meteor 1.0 - Hosting a lobby and redirecting other users

I have a problem for which I do not know the solution. I am creating game that involves multiple players. One person "hosts" the game and sends "invitations" to other users to join the "lobby". If you've ever played Call of Duty or any similar game, it's the same concept.
Much of this process currently works properly.
I created a collection called Lobby to keep track of all the open and closed lobbies. When a user wants to host a lobby, he clicks on the button, which creates a new lobby and redirects the user to the proper url:
Template.lobby.events
'click button#host-lobby': (e,t) ->
lobbyId = Lobby.insert
host: Meteor.user()._id
hostName: Meteor.user().username
status: true
players: []
Router.go("currentLobby", _id: lobbyId)
Then, the user can invite other users to the lobby (url) through a modal, which adds a notification object to the invited user's profile. Probably not the best way to do it, so I'm open to suggestions on this front.
Template._playerItem.events
'click button': (e,t) ->
lobbyId = Session.get "currentLobby"
Meteor.call "sendNotification", #_id, lobbyId, Meteor.user().username
Lobby.update
_id: lobbyId
,
$addToSet:
invitedPlayers: #_id
And the method:
Meteor.methods
sendNotification: (userId, lobbyId, hostName) ->
sendTo = Meteor.users.findOne(_id: userId)
Meteor.users.update
_id: userId
,
$push:
invite:
hostName: hostName
lobbyId: lobbyId
So at this point, the user can either accept or decline the invitation. If he accepts, he is routed to the lobby and is added to the players array in the lobby object. The user shows up in the list of players as one would expect.
My issue starts when I attempt to "start" the game. When the button is clicked, the game is created properly and the host (who pressed the button) is routed to the url for the new game:
Template.currentLobby.events
'click #start-game': (e,t) ->
playerIds = [#host]
#players.forEach (player) ->
playerIds.push(player.id)
Meteor.call 'createGame', playerIds
Router.go('home')
The problem is that the other users in the lobby are not redirected. They have access to the game if they manually go to the url, but they aren't taken there. They have no way of knowing that the game has actually started...
One solution is to add a "Game has started" badge, with a link to the game, but I think a more elegant solution is to route all of the users at the current lobby url to the game that was just started.
Is this functionality possible? Is there a better way to host a lobby?
EDIT
Thank you Chet for the solution. This is how I eventually implemented it:
Template.currentLobby.rendered = ->
#autorun ->
data = Template.currentData()
if data.url
Router.go data.url
#autorun had some context difficulties, so I just used the lobby data. Then, when someone clicks on the "start game" button, the current lobby is updated with the url of the new game (The Meteor.call 'createGame' returns the _id of the new game).
Template.currentLobby.events
'click #start-game': (e,t) ->
playerIds = [#host]
lobbyId = Template.currentData()._id
#players.forEach (player) ->
playerIds.push(player.id)
Meteor.call 'createGame', playerIds, (err, res) ->
Lobby.update
_id: lobbyId
,
$set:
url: "/game/#{res}"
Works like a charm. Thanks!
OK. First, to address your notifications. This is a valid solution. However, you'll probably want to sort them by date! You'll also want to create a separate Notifications collection. Anytime you update a document, the whole thing is send over DDP to the client. Thus any new notification added to the profile will result the entire profile being sent to the client. You'll also want some way of marking a notification as being read so it can be deleted.
To address you question about redirecting the lobby, create property called url. Initially it is set to false. Once the host is ready to start the game, they set the url property to the url.
When a user enters a lobby, start an autorun for the redirect.
Template.lobby.rendered = ->
#autorun ->
if game.url
Router.go(game.url)
You'll have to make sure that the game.url is a reactive datasource. If you pass the game as the data context using iron router, then you should be able to use #data.url but I'm not 100% sure it will be reactive. Just to be safe, try Games.findOne(#data._id) -- that will certainly be reactive.
EDIT:
Just to be clear, try this:
Template.lobby.rendered = ->
#autorun ->
game = Games.findOne(#data._id)
if game.url
Router.go(game.url)

Resources