Best JavaScript code snippet using playwright-internal
event-emitter.js
Source: event-emitter.js
...12 })13 describe('.create()', function () {14 it('should create a mixin with EventEmitter functionality', function () {15 var em = EventEmitter.create({})16 em.initEvents()17 for (var k in EventEmitter.prototype) {18 if (!hasOwnProp(EventEmitter.prototype, k) || typeof EventEmitter.prototype[k] !== 'function') continue19 assert(k in em && typeof em[k] === 'function', 'it should have a ' + k + ' method')20 }21 assert(em._listeners, 'it should have a listeners map')22 })23 })24 describe('.inherit()', function () {25 it('should make the class inherit from EventEmitter', function () {26 var MyEvEm = EventEmitter.inherit(function MyEvEm () {})27 var em = new MyEvEm()28 em.initEvents()29 assert(em instanceof EventEmitter, 'it should be an EventEmitter instance')30 assert(em._listeners, 'it should have a listeners map')31 })32 it('should preserve the prototype', function () {33 function MyEvEm () {}34 MyEvEm.prototype.foo = function () {}35 EventEmitter.inherit(MyEvEm)36 assert('foo' in MyEvEm.prototype)37 })38 })39 describe('#initEvents', function () {40 it('should add a listeners map to the instance', function () {41 var em = EventEmitter()42 assert(!em._listeners)43 em.initEvents()44 assert(em._listeners)45 })46 })47 describe('#emit()', function () {48 it('should emit an event to the listeners', function () {49 var em = new EventEmitter()50 em.initEvents()51 var fired52 em.on('foo', function (a) { fired = a })53 em.emit('foo', [true])54 em.emit('bar', [false])55 assert(fired)56 })57 })58 describe('#on', function () {59 it('should attach a callback to an event', function () {60 var em = new EventEmitter()61 em.initEvents()62 var fired63 em.on('foo', function (a) { fired = a })64 em.emit('foo', [true])65 assert(fired)66 })67 })68 describe('#once', function () {69 it('should attach a callback only to the next instance of the event', function () {70 var em = new EventEmitter()71 em.initEvents()72 var fired73 em.once('foo', function (a) { fired = a })74 em.emit('foo', [true])75 em.emit('foo', [false])76 assert(fired)77 })78 })79 describe('#off', function () {80 it('should detach a callback from an event', function () {81 var em = new EventEmitter()82 em.initEvents()83 var fired84 var cb = function (a) { fired = a }85 em.on('foo', cb)86 em.emit('foo', [true])87 em.off('foo', cb)88 em.emit('foo', [false])89 assert(fired)90 })91 it('should be race-safe', function () {92 var em = new EventEmitter()93 em.initEvents()94 var fired95 var cb1 = function () { em.off('foo', cb1) }96 var cb2 = function (a) { fired = a }97 em.on('foo', cb1)98 em.on('foo', cb2)99 em.emit('foo', [true])100 assert(fired)101 })102 })103 describe('#proxy', function () {104 it('should proxy the calls to the function to the specified event', function () {105 var em = new EventEmitter()106 em.initEvents()107 var fired108 var cb = function (a) { fired = a }109 em.on('foo', cb)110 em.proxy('foo')(true)111 assert(fired)112 })113 })...
app.js
Source: app.js
1/**2 * Init registered modules on specified events3 *4 * @license APLv25 */6import namespace from './namespace';7import loadPolyfills from './polyfills';8/** Demo modules * */9import SkipLinks from '../../../demo/modules/skiplinks/skiplinks';10import SlideShow from '../../../demo/modules/slideshow/slideshow';11/* autoinsertmodulereference */ // eslint-disable-line12class App {13 constructor() {14 // Module instances15 window[namespace].modules = {};16 this.initEvents = [];17 // Module registry - mapping module name (used in data-init) to module Class18 this.modules = {};19 this.modules.slideshow = SlideShow;20 this.modules.skiplinks = SkipLinks;21 /* autoinsertmodule */ // eslint-disable-line22 // expose initModule function23 window[namespace].helpers.initModule = this.initModule;24 }25 async start() {26 await loadPolyfills();27 this.registerModules();28 this.initModuleInitialiser();29 }30 initModule(moduleName, element) {31 const Module = window[namespace].modules[moduleName].Class;32 const metaData = this.parseData(element, `${moduleName}Data`);33 const metaOptions = this.parseData(element, `${moduleName}Options`);34 const moduleInstance = new Module(element, metaData, metaOptions);35 window[namespace].modules[moduleName].instances[moduleInstance.uuid] = moduleInstance;36 element.dataset[`${moduleName}Instance`] = moduleInstance.uuid; // eslint-disable-line no-param-reassign37 }38 registerModules() {39 [].slice.call(document.querySelectorAll('[data-init]')).forEach((element) => {40 const modules = element.dataset.init.split(' ');41 modules.forEach((moduleName) => {42 this.registerModule(moduleName);43 });44 });45 }46 registerModule(moduleName) {47 if (!window[namespace].modules[moduleName] && this.modules[moduleName]) {48 const Module = this.modules[moduleName];49 window[namespace].modules[moduleName] = {50 initEvents: Module.initEvents,51 events: Module.events,52 instances: {},53 Class: Module,54 };55 this.initEvents = this.initEvents.concat(Module.initEvents);56 // Remove duplicates from initEvents57 this.initEvents = [...new Set(this.initEvents)];58 }59 }60 isRegistered(moduleName) {61 return window[namespace].modules[moduleName];62 }63 isInitialised(element, moduleName) {64 return element.dataset[`${moduleName}Instance`];65 }66 isInitEvent(eventType, moduleName) {67 return window[namespace].modules[moduleName].initEvents.indexOf(eventType) !== -1;68 }69 initModules(event) {70 [].slice.call(document.querySelectorAll('[data-init]')).forEach((element) => {71 const modules = element.dataset.init.split(' ');72 modules.forEach((moduleName) => {73 if (this.isRegistered(moduleName)74 && !this.isInitialised(element, moduleName)75 && this.isInitEvent(event.type, moduleName)) {76 this.initModule(moduleName, element);77 }78 });79 });80 }81 initModuleInitialiser() {82 if (!this.initEvents.length) {83 return;84 }85 this.initEvents.forEach((event) => {86 document.addEventListener(event, this.initModules.bind(this), false);87 });88 }89 parseData(element, key) {90 const data = element.dataset[key];91 if (!data) {92 return null;93 }94 try {95 return JSON.parse(data);96 } catch (err) {97 console.log(`Failed when parsing "${data}"`, element, err);98 return null;99 }100 }101}...
Events.js
Source: Events.js
...46nextTick.reset();47coreAppend(Class, {48 Events: Class({49 addEvent: function(name, fn) {50 initEvents(this);51 var i, l, onfinish = [];52 if (arguments.length == 1 && typeof name != 'string') {53 for (i in name) {54 this.addEvent(i, name[i]);55 }56 } else if (Array.isArray(name)) {57 for (i = 0, l = name.length; i < l; i++) {58 this.addEvent(name[i], fn);59 }60 } else {61 name = removeOn(name);62 if (name == '$ready') {63 throw new TypeError('Event name «$ready» is reserved');64 } else if (!fn) {65 throw new TypeError('Function is empty');66 } else {67 Object.ifEmpty(this._events, name, []);68 this._events[name].include(fn);69 var ready = this._events.$ready[name];70 if (ready) fire.apply(this, [name, fn, ready, onfinish]);71 onfinish.invoke();72 }73 }74 return this;75 },76 removeEvent: function (name, fn) {77 if (!arguments.length) {78 initEvents( this, true );79 return this;80 }81 initEvents(this);82 if (Array.isArray(name)) {83 for (var i = name.length; i--;) {84 this.removeEvent(name[i], fn);85 }86 } else if (arguments.length == 1 && typeof name != 'string') {87 for (i in name) {88 this.removeEvent(i, name[i]);89 }90 } else {91 name = removeOn(name);92 if (name == '$ready') {93 throw new TypeError('Event name «$ready» is reserved');94 } else if (arguments.length == 1) {95 this._events[name] = [];96 } else if (name in this._events) {97 this._events[name].erase(fn);98 }99 }100 return this;101 },102 isEventAdded: function (name) {103 initEvents(this);104 105 var e = this._events[name];106 return !!(e && e.length);107 },108 fireEvent: function (name, args) {109 initEvents(this);110 111 name = removeOn(name);112 // we should prevent skipping next event on removing this in different fireEvents113 var funcs = atom.clone(this._events[name]);114 if (funcs) {115 var l = funcs.length,116 i = 0;117 for (;i < l; i++) fire.call(this, name, funcs[i], args || []);118 }119 return this;120 },121 readyEvent: function (name, args) {122 initEvents(this);123 124 nextTick(function () {125 name = removeOn(name);126 this._events.$ready[name] = args || [];127 this.fireEvent(name, args || []);128 }.bind(this));129 return this;130 }131 })132});...
estaticoapp.js
Source: estaticoapp.js
1/**2 * Init registered modules on specified events3 *4 * @license APLv25 */6import $ from 'jquery';7/** Demo modules **/8import SkipLinks from '../../../demo/modules/skiplinks/skiplinks';9import SlideShow from '../../../demo/modules/slideshow/slideshow';10/* autoinsertmodulereference */ // eslint-disable-line11class EstaticoApp {12 constructor() {13 // Module instances14 window.estatico.modules = {};15 this.initEvents = [];16 // Module registry - mapping module name (used in data-init) to module Class17 this.modules = {};18 this.modules.slideshow = SlideShow;19 this.modules.skiplinks = SkipLinks;20 /* autoinsertmodule */ // eslint-disable-line21 // expose initModule function22 estatico.helpers.initModule = this.initModule;23 }24 start() {25 this._registerModules();26 this._initModuleInitialiser();27 }28 initModule(moduleName, $node) {29 let Module = estatico.modules[moduleName].Class,30 _metaData = $node.data(moduleName + '-data') || {},31 _metaOptions = $node.data(moduleName + '-options') || {},32 moduleInstance = new Module($node, _metaData, _metaOptions);33 estatico.modules[moduleName].instances[moduleInstance.uuid] = moduleInstance;34 $node.data(moduleName + 'Instance', moduleInstance);35 }36 _registerModules() {37 $('[data-init]').each((key, element) => {38 let modules = $(element).data('init').split(' ');39 modules.forEach((moduleName) => {40 this._registerModule(moduleName);41 });42 });43 }44 _registerModule(moduleName) {45 if (!estatico.modules[moduleName] && this.modules[moduleName]) {46 let Module = this.modules[moduleName];47 estatico.modules[moduleName] = {48 initEvents: Module.initEvents,49 events: Module.events,50 instances: {},51 Class: Module52 };53 this.initEvents = this.initEvents.concat(Module.initEvents);54 // Remove duplicates from initEvents55 this.initEvents = [...new Set(this.initEvents)];56 }57 }58 _isRegistered(moduleName) {59 return estatico.modules[moduleName];60 }61 _isInitialised($element, moduleName) {62 // jQuery 3 does not allow kebab-case in data() when retrieving whole data object https://jquery.com/upgrade-guide/3.0/#breaking-change-data-names-containing-dashes63 return $element.data(moduleName + 'Instance');64 }65 _isInitEvent(eventType, moduleName) {66 return estatico.modules[moduleName].initEvents.indexOf(eventType) !== -1;67 }68 _initModules(event) {69 $('[data-init]').each((key, element) => {70 let $element = $(element),71 modules = $element.data('init').split(' ');72 modules.forEach((moduleName) => {73 if (this._isRegistered(moduleName) && !this._isInitialised($element, moduleName) && this._isInitEvent(event.type, moduleName)) {74 this.initModule(moduleName, $element);75 }76 });77 });78 }79 _initModuleInitialiser() {80 if (!this.initEvents.length) {81 return;82 }83 // jQuery 3 does not support `ready` event in $(document).on() https://jquery.com/upgrade-guide/3.0/#breaking-change-on-quot-ready-quot-fn-removed84 // But lets sent 'ready' information to modules initialising on that event85 $(this._initModules.bind(this, { type: 'ready' }));86 $(document).on(this.initEvents.join(' '), this._initModules.bind(this));87 }88}...
JcropTouch.js
Source: JcropTouch.js
...22 p = $.Jcrop.component.DragState.prototype;23 // A bit of an ugly hack to make sure we modify prototype24 // only once, store a key on the prototype25 if (!p.touch) {26 t.initEvents();27 t.shimDragState();28 t.shimStageDrag();29 p.touch = true;30 }31 },32 // }}}33 // shimDragState: function(){{{34 shimDragState: function(){35 var t = this;36 $.Jcrop.component.DragState.prototype.initEvents = function(e){37 38 // Attach subsequent drag event handlers based on initial39 // event type - avoids collecting "pseudo-mouse" events40 // generated by some mobile browsers in some circumstances41 if (e.type.substr(0,5) == 'touch') {42 $(this.eventTarget)43 .on('touchmove.jcrop.jcrop-touch',t.dragWrap(this.createDragHandler()))44 .on('touchend.jcrop.jcrop-touch',this.createStopHandler());45 }46 47 // For other events, use the mouse handlers that48 // the default DragState.initEvents() method sets...49 else {50 $(this.eventTarget)51 .on('mousemove.jcrop',this.createDragHandler())52 .on('mouseup.jcrop',this.createStopHandler());53 }54 };55 },56 // }}}57 // shimStageDrag: function(){{{58 shimStageDrag: function(){59 this.core.container60 .addClass('jcrop-touch')61 .on('touchstart.jcrop.jcrop-stage',this.dragWrap(this.core.ui.manager.startDragHandler()));62 },...
preloader.js
Source: preloader.js
1/* eslint-disable wrap-iife */2/* global jQuery */3// Preloader4( function( $ ) {5 function preloader( config = {} ) {6 // Create return object7 const object = {8 config: {9 initEvents: ['setupfinish', 'brandscapeload', 'dynamicheightguyload'],10 preloaderClass: 'js-preloader',11 removalDelay: 10012 },13 preloaders: []14 };15 // Create object config16 object.config = Object.assign( object.config, config );17 // Init18 object.init = function() {19 // Query up preloaders based on selector20 object.preloaders = $( '.' + object.config.preloaderClass );21 // Add initEvents to object22 object.initEvents = object.config.initEvents;23 // Count down until all required initEvents have been triggered, then remove the preloader24 $( window ).one( object.initEvents.join( ' ' ), function( event ) {25 // Remove the initEvent from the array26 object.initEvents = object.initEvents.filter( ( initEvent ) => initEvent !== event.type );27 // If the initEvents are empty, remove the preloader28 if ( !object.initEvents.length ) {29 // Remove active class after delay time30 window.setTimeout(31 function() {32 // Remove active class33 object.preloaders.removeClass( 'is-active' );34 // Remove locked by popover class from HTML35 $( 'html' ).removeClass( 'locked-by-popover' );36 // Fire off event37 $( window ).trigger( 'preloaderremove' );38 },39 object.config.removalDelay40 );41 }42 } );43 };44 return object;45 }46 // Instantiate and initialize a new preloader object47 const thePreloader = Object.create( preloader() );48 thePreloader.init();...
main.js
Source: main.js
1var common;2common = new Analysis();3common.initEvents();4AUI().ready(function(A) {5});6Liferay.Portlet.ready(7 function(portletId, node) {8 var portletName = portletId.split("_")[0];9 var mh, me, ct, pd, rc, vc, mp, bp, mo, np;10 if (portletName == "materialshome") {11 mh = new MaterialsHome(common);12 mh.initEvents(); 13 } else if (portletName == "materialsexplorer") {14 me = new MaterialsExplorer(common);15 me.initEvents();16 } else if (portletName == "crystaltoolkit") {17 ct = new CrystalToolkit(common);18 ct.initEvents();19 } else if (portletName == "phasediagram") {20 pd = new PhaseDiagram(common);21 pd.initEvents();22 } else if (portletName == "reactioncalculator") {23 rc = new ReactionCalculator(common);24 rc.initEvents();25 } else if (portletName == "visualizationchart") {26 vc = new VisualizationChart(common);27 vc.initEvents(); 28 } else if (portletName == "mlpredictor") {29 mp = new MLPredictor(common);30 mp.initEvents();31 } else if (portletName == "batteryexplorer") {32 bp = new BatteryExplorer(common);33 bp.initEvents();34 } else if (portletName == "monitoringportlet") {35 mo = new Monitoring(common);36 mo.initEvents();37 } else if (portletName == "nanoporousexplorer") {38 np = new NanoporousExplorer(common);39 np.initEvents();40 } else if (portletName == "nanoporousanalysis") {41 np = new NanoporousAnalysis(common);42 np.initEvents();43 }44 }45);46Liferay.on('allPortletsReady', function() { ...
Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch({ headless: false });4 const page = await browser.newPage();5 await page.waitForSelector('text=Google');6 await page.click('text=Google');7 await page.waitForSelector('text=I\'m Feeling Lucky');8 await page.click('text=I\'m Feeling Lucky');9 await page.waitForSelector('text=Google Search');10 await page.click('text=Google Search');11 await browser.close();12})();
Using AI Code Generation
1const { initEvents } = require('playwright/lib/server/events');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch({ headless: false });5 const context = await browser.newContext();6 const page = await context.newPage();7 initEvents(page);8 page.on('request', (request) => {9 console.log('Request URL: ' + request.url());10 });11 await page.click('text=Get started');12 await browser.close();13})();
Using AI Code Generation
1const { initEvents } = require('./events.js');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch();5 const page = await browser.newPage();6 await initEvents(page);7 await page.click('text=Get started');8 await browser.close();9})();10const { events } = require('playwright-core/lib/protocol/protocol');11const { EventEmitter } = require('events');12class Events extends EventEmitter {13 constructor() {14 super();15 }16}17const eventsInstance = new Events();18module.exports = {19 initEvents: async (page) => {20 await page._client.send('Target.setAutoAttach', {21 });22 page._client.on('Target.attachedToTarget', async ({ sessionId }) => {23 const session = await page._client.send('Target.attachToTarget', {24 });25 session.on('Target.receivedMessageFromTarget', ({ message }) => {26 const parsedMessage = JSON.parse(message);27 if (parsedMessage.method === 'Network.requestWillBeSent') {28 eventsInstance.emit('request', parsedMessage.params.request);29 }30 });31 });32 return eventsInstance;33 }34};35const { chromium } = require('playwright');36(async () => {37 const browser = await chromium.launch();38 const page = await browser.newPage();39 await page.route('**', route => {40 const request = route.request();41 console.log("request body", request.body);42 route.continue();43 });44 await page.click('text=Get started');45 await browser.close();46})();47const { chromium } = require('playwright');48(async () => {49 const browser = await chromium.launch();50 const page = await browser.newPage();
Using AI Code Generation
1const { initEvents } = require('playwright/lib/client/page');2initEvents(page);3const { initEvents } = require('playwright/lib/client/frame');4initEvents(frame);5page.on(eventType, callback)6frame.on(eventType, callback)7Argument Description event The event object. The properties of this object are different for each event type. The following is the event object for the 'request' event: { url: string,
Using AI Code Generation
1const { initEvents } = require('@playwright/test/lib/server/events');2const events = initEvents();3events.on('test', (testInfo) => {4 console.log(testInfo);5});6const { initEvents } = require('@playwright/test/lib/server/events');7const events = initEvents();8events.on('test', (testInfo) => {9 console.log(testInfo);10});11const { initEvents } = require('@playwright/test/lib/server/events');12const events = initEvents();13events.on('test', (testInfo) => {14 console.log(testInfo);15});16const { initEvents } = require('@playwright/test/lib/server/events');17const events = initEvents();18events.on('test', (testInfo) => {19 console.log(testInfo);20});21const { initEvents } = require('@playwright/test/lib/server/events');22const events = initEvents();23events.on('test', (testInfo) => {24 console.log(testInfo);25});26const { initEvents } = require('@playwright/test/lib/server/events');27const events = initEvents();28events.on('test', (testInfo) => {29 console.log(testInfo);30});31const { initEvents } = require('@playwright/test/lib/server/events');32const events = initEvents();33events.on('test', (testInfo) => {34 console.log(testInfo);35});36const { initEvents } = require('@playwright/test/lib/server/events');37const events = initEvents();38events.on('test', (testInfo) => {39 console.log(testInfo);40});41const { initEvents } = require('@playwright/test/lib/server/events');42const events = initEvents();43events.on('test', (testInfo) => {44 console.log(testInfo);45});46const { initEvents }
Using AI Code Generation
1const { initEvents } = require('playwright/lib/server/events');2const events = initEvents();3events.on('event', (event) => console.log(event));4events.on('close', () => console.log('close'));5const { initEvents } = require('playwright/lib/server/events');6const events = initEvents();7events.on('event', (event) => console.log(event));8events.on('close', () => console.log('close'));9const { initEvents } = require('playwright/lib/server/events');10const events = initEvents();11events.on('event', (event) => console.log(event));12events.on('close', () => console.log('close'));13const { initEvents } = require('playwright/lib/server/events');14const events = initEvents();15events.on('event', (event) => console.log(event));16events.on('close', () => console.log('close'));17const { initEvents } = require('playwright/lib/server/events');18const events = initEvents();19events.on('event', (event) => console.log(event));20events.on('close', () => console.log('close'));21const { initEvents } = require('playwright/lib/server/events');22const events = initEvents();23events.on('event', (event) => console.log(event));24events.on('close', () => console.log('close'));25const { initEvents } = require('playwright/lib/server/events');26const events = initEvents();27events.on('event', (event) => console.log(event));28events.on('close', () => console.log('close'));29const { initEvents } = require('playwright/lib/server/events');30const events = initEvents();31events.on('event', (event) => console.log(event));32events.on('close', () => console.log('close'));33const { initEvents } = require('playwright/lib/server/events');34const events = initEvents();35events.on('event', (event) => console.log(event));
Using AI Code Generation
1const { initEvents } = require('playwright/lib/server/events');2const { events } = initEvents({ enabled: true });3events.on('event', (event) => {4 console.log(event);5});6const playwright = require('playwright');7(async () => {8 const browser = await playwright.chromium.launch();9 const context = await browser.newContext();10 const page = await context.newPage();11 await browser.close();12})();
Jest + Playwright - Test callbacks of event-based DOM library
firefox browser does not start in playwright
Is it possible to get the selector from a locator object in playwright?
How to run a list of test suites in a single file concurrently in jest?
Running Playwright in Azure Function
firefox browser does not start in playwright
This question is quite close to a "need more focus" question. But let's try to give it some focus:
Does Playwright has access to the cPicker object on the page? Does it has access to the window object?
Yes, you can access both cPicker and the window object inside an evaluate call.
Should I trigger the events from the HTML file itself, and in the callbacks, print in the DOM the result, in some dummy-element, and then infer from that dummy element text that the callbacks fired?
Exactly, or you can assign values to a javascript variable:
const cPicker = new ColorPicker({
onClickOutside(e){
},
onInput(color){
window['color'] = color;
},
onChange(color){
window['result'] = color;
}
})
And then
it('Should call all callbacks with correct arguments', async() => {
await page.goto(`http://localhost:5000/tests/visual/basic.html`, {waitUntil:'load'})
// Wait until the next frame
await page.evaluate(() => new Promise(requestAnimationFrame))
// Act
// Assert
const result = await page.evaluate(() => window['color']);
// Check the value
})
Check out the latest blogs from LambdaTest on this topic:
Native apps are developed specifically for one platform. Hence they are fast and deliver superior performance. They can be downloaded from various app stores and are not accessible through browsers.
One of the essential parts when performing automated UI testing, whether using Selenium or another framework, is identifying the correct web elements the tests will interact with. However, if the web elements are not located correctly, you might get NoSuchElementException in Selenium. This would cause a false negative result because we won’t get to the actual functionality check. Instead, our test will fail simply because it failed to interact with the correct element.
Smartphones have changed the way humans interact with technology. Be it travel, fitness, lifestyle, video games, or even services, it’s all just a few touches away (quite literally so). We only need to look at the growing throngs of smartphone or tablet users vs. desktop users to grasp this reality.
As part of one of my consulting efforts, I worked with a mid-sized company that was looking to move toward a more agile manner of developing software. As with any shift in work style, there is some bewilderment and, for some, considerable anxiety. People are being challenged to leave their comfort zones and embrace a continuously changing, dynamic working environment. And, dare I say it, testing may be the most ‘disturbed’ of the software roles in agile development.
LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!