When I write tests to run in playwright, I would like to be able to set the date that the browser believes it to be at the start of the test. Is there a way to achieve this using playwright?
Way easier solution with
page.evaluate(() => {
Date.now = () => {
return 1609477200000; //Jan 1, 2021
};
});
You can use mocking tools like TimeShift.
This piece of code would inject the timeshift library and then then the date to Feb 02 2012:
const playwright = require("playwright");
(async () => {
let removed = false;
const browser = await playwright["chromium"].launch({headless: false});
const page = await browser.newPage();
await page.setContent('Test me<span></span>');
await page.click('a');
console.log(await page.innerText('span'));
await page.addScriptTag({ url: 'https://cdn.jsdelivr.net/npm/timeshift-js#1.1.1/timeshift.js'});
await page.evaluate(() => {
Date = TimeShift.Date;
TimeShift.setTime(1328230923000);
});
await page.click('a');
console.log(await page.innerText('span'));
browser.close();
})();
Output:
Fri Sep 18 2020 15:06:58 GMT-0300 (Argentina Standard Time)
Thu Feb 02 2012 22:02:03 GMT-0300
Version without tick functionality:
page.on('load', () => {
page.evaluate(() => {
const OriginalDate = window.Date;
class MockDate extends OriginalDate {
static currentDate = '18 May 2018 12:00 UTC';
static currentTimeStamp = (new OriginalDate(MockDate.currentDate)).getTime();
constructor(...args) {
const params = (args && args.length) ? args : [MockDate.currentTimeStamp];
super(...params);
}
static [Symbol.hasInstance](instance: Date) {
return typeof instance.getDate === 'function';
}
static now() {
return MockDate.currentTimeStamp;
}
}
window.Date = MockDate;
});
});
await page.goto(yourUrl);
Version with tick:
page.on('load', () => {
page.evaluate(() => {
const OriginalDate = window.Date;
class MockDate extends OriginalDate {
static currentDate = '18 May 2018 12:00 UTC';
static currentTimeStamp = (new OriginalDate(MockDate.currentDate)).getTime();
static originalNow = OriginalDate.now();
constructor(...args) {
const params = (args && args.length) ? args : [(MockDate.currentTimeStamp + MockDate.getTick())];
super(...params);
}
static [Symbol.hasInstance](instance: Date) {
return typeof instance.getDate === 'function';
}
static getTick() {
return OriginalDate.now() - MockDate.originalNow;
}
static now() {
return MockDate.currentTimeStamp + MockDate.getTick();
}
}
window.Date = MockDate;
});
});
await page.goto(yourUrl);
I have found one simple solution in a GitHub Issue
async clock(value) {
const date = new Date(value).valueOf();
await page.addInitScript(`{
// Extend Date constructor to default to fakeNow
Date = class extends Date {
constructor(...args) {
if (args.length === 0) {
super(${date.valueOf()});
} else {
super(...args);
}
}
}
// Override Date.now() to start from fakeNow
const __DateNowOffset =${date.valueOf()} - Date.now();
const __DateNow = Date.now;
Date.now = () => __DateNow() + __DateNowOffset;
}`);
}
await clock("2022/01/01")
Related
I have an app that allows users to schedule tasks. Users can set a schedule (schedule, an rrule string) as well as a timezone (scheduleTimeZone, a string e.g. Asia/Dubai).
I am trying to write a function (getNextRunAt) that gets the next occurrence of the task at a UTC date and store this in my Postgres DB as a timestamptz field.
I'm struggling to account for DST, lots of the results are an hour or even a day off.
Here's the function (in TypeScript):
import RRule from 'rrule';
import moment from 'moment-timezone';
const getNextRunAt = ({
schedule,
scheduleTimeZone,
}: {
schedule?: string | null;
scheduleTimeZone?: string | null;
}): Date | undefined => {
if (!schedule) {
return undefined;
}
const options = RRule.parseString(schedule);
if (scheduleTimeZone) {
options.tzid = scheduleTimeZone;
}
const dtstart = moment.utc().toDate();
const rule = new RRule({ ...options, dtstart, count: 1 });
const dates = rule.all();
let date = dates[0];
if (scheduleTimeZone && moment(date).tz(scheduleTimeZone).isDST()) {
date = moment(date).subtract(1, 'hour').toDate();
}
return date;
};
export default getNextRunAt;
It works for some dates/times/timezones:
Date.now = jest.fn(() => new Date('2021-03-02 10:24:27.000000Z').getTime());
const nextDate = getNextRunAt({
schedule: 'RRULE:INTERVAL=1;BYDAY=MO,TU,WE,TH,SA,FR,SU;BYMINUTE=0;BYHOUR=9;BYSECOND=0;FREQ=DAILY',
scheduleTimeZone: 'America/Los_Angeles',
});
expect(nextDate).toEqual(new Date('2021-03-02 17:00:00.000000Z'));
// WORKS
But not for others:
Date.now = jest.fn(() => new Date('2021-03-02 10:24:27.000000Z').getTime());
const nextDate = getNextRunAt({
schedule: 'RRULE:INTERVAL=1;BYDAY=MO,TU,WE,TH,SA,FR,SU;BYMINUTE=0;BYHOUR=9;BYSECOND=0;FREQ=DAILY',
scheduleTimeZone: 'America/Los_Angeles',
});
expect(nextDate).toEqual(new Date('2021-03-02 17:00:00.000000Z'));
// DOES NOT WORK
Expected: 2021-03-02T17:00:00.000Z
Received: 2021-03-03T17:00:00.000Z
I think that the last case is wrong with a day. But for others solution might look like:
const getNextRunAt = ({ schedule, scheduleTimeZone }: { schedule?: string | null; scheduleTimeZone?: string | null }): Date | undefined => {
if (!schedule) {
return undefined;
}
const options = RRule.parseString(schedule);
const dtstart = moment();
const rule = new RRule({
...options,
dtstart: dtstart.toDate(),
count: 1,
});
const dates = rule.all();
const date = dates[0];
let mDate = moment.tz(date, scheduleTimeZone);
const offset = Math.abs(mDate.utcOffset()) > 16 ? mDate.utcOffset() / 60 : mDate.utcOffset();
if (!mDate.isDST()) {
mDate = mDate.add(1, 'hours');
}
mDate.add(-offset, 'hour');
return mDate.toDate();};
I want to select the data before or after a certain date in sqlite using Typeorm where date is in milliseconds.
Here is my Query Builder:
const qb = getConnection()
.getRepository(Post)
.createQueryBuilder('p')
.where('createdAt < :cursor', {
cursor: "1624809058000"
})
.getMany()
We have to pass the date in this format: "yyyy-mm-dd HH:MM:ss.l" Example: "2000-01-01 00:00:00.000"
So, we have to convert the date in this format.
First solution
const chars = { T: ' ', Z: '' }
const dateInMilliseconds = "1624809058000"
const qb = getConnection()
.getRepository(Post)
.createQueryBuilder('p')
.where('createdAt < :cursor', {
cursor: new Date(parseInt(dateInMilliseconds))
.toISOString()
.replace(/[TZ]/g, ch => chars[ch])
})
.getMany()
Second solution
import { LessThan, MoreThan } from 'typeorm'
import { format } from 'date-fns'
// TypeORM Query Operators
const MoreThanDate = (date: Date) => MoreThan(format(date, 'yyyy-mm-dd HH:MM:SS'))
const LessThanDate = (date: Date) => LessThan(format(date, 'yyyy-mm-dd HH:MM:SS'))
const dateInMilliseconds = "1624809058000"
const posts = await Post.find({
createdAt: MoreThanDate(new Date(dateInMilliseconds))
})
Third solution
import { Between } from 'typeorm';
import { addYears, subYears } from 'date-fns';
// TypeORM Query Operators
const MoreThanDate = (date: Date) => Between(date, addYears(date, 100));
const LessThanDate = (date: Date) => Between(subYears(date, 100), date);
const dateInMilliseconds = "1624809058000"
const posts = await Post.find({
where: {
createdAt: MoreThanDate(new Date(dateInMillisecons)),
},
});
I have function called setMockDate that looks like:
/**
* #param {Date} expected
* #returns {Function} Call to remove Date mocking
*/
const setMockDate = (expected: Date): AnyObject => {
const RealDate = Date
function MockDate(mockOverride?: Date | number) {
return new RealDate(mockOverride || expected)
}
MockDate.UTC = RealDate.UTC
MockDate.parse = RealDate.parse
MockDate.now = () => expected.getTime()
MockDate.prototype = RealDate.prototype
// eslint-disable-next-line #typescript-eslint/no-explicit-any
global.Date = MockDate as any
return () => {
global.Date = RealDate
}
}
export default setMockDate
and used like:
test('Should change date', () => {
const mockDate = new Date('Feb 22, 2021 11:59:00')
setMockDate(mockDate)
expect(new Date()).toEqual(mockDate)
})
I wanted to change the MockDate function in setMockDate() to use an arrow function like:
const MockDate = (mockOverride?: Date | number) => {
return new RealDate(mockOverride || expected)
}
However I get TypeError:
TypeError: Date is not a constructor
Why I am getting this error when changing to use an arrow function?
MockDate is supposed to mimic new Date() which is a constructor and arrow function cannot be used as a constructor
More information here:
https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
I updated my helper function to:
import { AnyObject } from 'typings/custom'
/**
* #param {Date} expected
* #returns {Function} Call to remove Date mocking
*/
const setMockDate = (expected: Date): AnyObject => {
const RealDate = Date
function MockDate(mockOverride?: Date | number) {
return new RealDate(mockOverride || expected)
}
MockDate.now = () => expected.getTime()
MockDate.prototype = RealDate.prototype
// eslint-disable-next-line #typescript-eslint/no-explicit-any
global.Date = MockDate as any
return () => {
global.Date = RealDate
}
}
export default setMockDate
Using this helper looks like:
/**
* Testing a component that changes text based on time of day
* ie: good morning, good afternoon & good evening
*/
describe('Greeting', () => {
it('Should update message after time', () => {
jest.useFakeTimers()
setMockDate(new Date('Feb 22, 2021 11:59:00'))
const wrapper = mount(<Greeting />)
const greetingText = wrapper.text()
setMockDate(new Date('Feb 22, 2021 12:00:00'))
jest.advanceTimersByTime(60000)
expect(wrapper.text()).not.toBe(greetingText)
})
})
export const fetchDailyData = async () => {
try {
const { data } = await axios.get(`${url}/daily`);
let today = new Date('2020-01-27');
let referanceDay = new Date(dailyData.reportDate)
const modifiedData = data.map((dailyData) => ({
if(referanceDay => today){
confirmed: dailyData.confirmed.total,
deaths: dailyData.deaths.total,
date: dailyData.reportDate
};
}))
// return modifiedData
} catch (error) {
}
}
I am trying to compare days and return proper ones. But it's not worked. I think I got a mistake object and if section. Could you please look my problem. Thanks..
Daily data is argument to anon. Function but being referenced out in which case it would be undefined.
It looks like your >= comparison in your if statement is backwards. Also you are referencing dailyData before it is declared inside mapTry this:
export const fetchDailyData = async () => {
try {
const { data } = await axios.get(`${url}/daily`);
let today = new Date('2020-01-27');
const modifiedData = data.map((dailyData) => ({
let referanceDay = new Date(dailyData.reportDate)
if(referanceDay >= today){
confirmed: dailyData.confirmed.total,
deaths: dailyData.deaths.total,
date: dailyData.reportDate
};
}))
// return modifiedData
} catch (error) {
}
}
I think you should use a library to compare the dates. I use Moment.js for all my datetime operations.
enter link description here
example from docs :
moment('2010-10-20').isAfter('2010-01-01', 'year'); // false
I am saving state in my React app via localStorage
const [items, setItem] = useState(() => {
let itemsString = window.localStorage.getItem('items');
if (itemsString) {
try {
return JSON.parse(itemsString);
} catch (e) {
console.error(e);
return [];
}
} else {
return [];
}
})
when I JSON.parse(itemsString) the date in the state has been converted to UTC (because strings/localstorage)
How do I JSON.parse() my state and reinitialize the date string to an object?
e.g. instead of returning 2019-07-19T00:28:03.058Z return Thu Jul 18 2019 20:28:03 GMT-0400 (Eastern Daylight Time) instead
Solution I came up with the help of Aaron's suggestion below.
Store old state. Map over state array, creating new empty object and storing each property in it, store date, convert date to string and pass that into new Date object to instantiate the value back to a date object on page refresh.
const [items, setItem] = useState(() => {
let itemsString = window.localStorage.getItem('items');
if (itemsString) {
try {
const oldState = JSON.parse(itemsString);
const newState = oldState.map(item => {
const date = item.date;
const container = {}
container.id = item.id
container.item = item.item
container.date = new Date(date.toString())
container.cost = item.cost
container.type = item.type
return container;
})
return newState;
} catch (e) {
console.error(e);
return [];
}
} else {
return [];
}
})
After you parse itemString just set the date key to a new Date object
const object = JSON.parse(itemString);
const newState = {...object, date: new Date(object.date)}