Best JavaScript code snippet using playwright-internal
map.js
Source: map.js
...107 let greyDirection;108 do {109 greyDirection = directions[Math.floor(Math.random() * directions.length)];110 }111 // while (greyDirection === getOppositeDirection(clrSegments[0][1]) === map[map.length - 1].relativePositionToLast || greyDirection === getOppositeDirection(map[map.length - 1].relativePositionToLast) || (mapOverlap(getOffsets(greyDirection))));112 while (greyDirection === getOppositeDirection(clrSegments[0][1]) === map[map.length - 1].relativePositionToLast || (mapOverlap(getOffsets(greyDirection))));113 map.push(new Tile(greyTileClr, greyShadowClr, greyDirection));114 offsets = getOffsets(greyDirection);115 shownTiles.push(offsets);116 }117 // first tile direction -> ensures that the entire sequence doesn't overlap118 let tileCounter = 0;119 if (i > 0) {120 let clrDirection = checkSequenceOverlap(clrSegments);121 while (clrDirection === true) {122 addGreyTile();123 clrDirection = checkSequenceOverlap(clrSegments);124 }125 directions = ['TL', 'TR', 'BL', 'BR'];126 map.push(new Tile(tileClr, shadowClr, clrDirection));127 offsets = getOffsets(clrDirection);128 shownTiles.push(offsets);129 tileCounter++;130 }131 // color shape132 for (let s = 0; s < clrSegments.length; s++) {133 for (; tileCounter <= clrSegments[s][0]; tileCounter++) {134 map.push(new Tile(tileClr, shadowClr, clrSegments[s][1]));135 if (tileCounter !== 0) {136 offsets = getOffsets(clrSegments[s][1]);137 shownTiles.push(offsets);138 }139 }140 }141 }142 }143 // for (let i = 0; i < map.length; i++) {144 // console.log(map[i]);145 // }146}147// return true if overlap; return the direction if not148function checkSequenceOverlap(clrSegments) {149 let storedOffsets = { x: 0, y: 0 };150 storedOffsets.x = offsets.x;151 storedOffsets.y = offsets.y;152 // first tile in the sequence: random direction153 let clrDirection = pickFirstDirection(clrSegments);154 if (clrDirection === true) {155 offsets = storedOffsets;156 directions = ['TL', 'TR', 'BL', 'BR'];157 return true;158 }159 offsets = getOffsets(clrDirection);160 // the following tiles: check overlap161 let t = 1;162 for (let s = 0; s < clrSegments.length; s++) {163 for (; t <= clrSegments[s][0]; t++) {164 // if any tile of the sequence is overlapped, start over from the first tile in the sequence165 if (mapOverlap(getOffsets(clrSegments[s][1]))) {166 offsets = storedOffsets;167 // pick another direction for the first tile168 clrDirection = pickFirstDirection(clrSegments);169 if (clrDirection === true) {170 offsets = storedOffsets;171 directions = ['TL', 'TR', 'BL', 'BR'];172 return true;173 }174 offsets = getOffsets(clrDirection);175 // start over the loop for checking the following tiles176 s = 0;177 t = 0;178 }179 else offsets = getOffsets(clrSegments[s][1]);180 }181 }182 // if no tile in the sequence overlaps, the direction of the first tile is perfect183 offsets = storedOffsets;184 return clrDirection;185}186// return true if no direction is left and still overlap, otherwise, return the direction picked187function pickFirstDirection(clrSegments) {188 let clrDirection;189 do {190 if (directions.length === 0) return true;191 shuffle(directions);192 clrDirection = directions[0];193 directions.splice(0, 1);194 } // not going reverse: not opposite to either the last tile or the next tile195 while ((getOppositeDirection(clrDirection) === clrSegments[0][1]) || (mapOverlap(getOffsets(clrDirection))));196 return clrDirection;197}198function addGreySequence() {199 let nGreyTiles = nTiles;200 let tileCounter = 0;201 for (; tileCounter < nGreyTiles; tileCounter++) {202 lastDirection = map[map.length - 1].relativePositionToLast;203 let greyDirection;204 do {205 if (directions.length === 0) {206 console.log("greysequence overlap not solvable");207 directions = ['TL', 'TR', 'BL', 'BR'];208 do {209 shuffle(directions);210 greyDirection = directions[0];211 directions.splice(0, 1);212 }213 while (getOppositeDirection(greyDirection) === lastDirection);214 directions = ['TL', 'TR', 'BL', 'BR'];215 map.push(new Tile(greyTileClr, greyShadowClr, greyDirection));216 offsets = getOffsets(greyDirection);217 shownTiles.push(offsets);218 return;219 }220 shuffle(directions);221 greyDirection = directions[0];222 directions.splice(0, 1);223 }224 while (getOppositeDirection(greyDirection) === lastDirection || mapOverlap(getOffsets(greyDirection)));225 directions = ['TL', 'TR', 'BL', 'BR'];226 map.push(new Tile(greyTileClr, greyShadowClr, greyDirection));227 offsets = getOffsets(greyDirection);228 shownTiles.push(offsets);229 }230}231function addGreyTile() {232 let greyDirection;233 do {234 if (directions.length === 0) {235 console.log("greytile overlap not solvable");236 directions = ['TL', 'TR', 'BL', 'BR'];237 do {238 shuffle(directions);239 greyDirection = directions[0];240 directions.splice(0, 1);241 }242 while (getOppositeDirection(greyDirection) === lastDirection);243 directions = ['TL', 'TR', 'BL', 'BR'];244 map.push(new Tile(greyTileClr, greyShadowClr, greyDirection));245 offsets = getOffsets(greyDirection);246 shownTiles.push(offsets);247 return;248 }249 shuffle(directions);250 greyDirection = directions[0];251 directions.splice(0, 1);252 }253 // while (greyDirection === getOppositeDirection(clrSegments[0][1]) === map[map.length - 1].relativePositionToLast || greyDirection === getOppositeDirection(map[map.length - 1].relativePositionToLast) || (mapOverlap(getOffsets(greyDirection))));254 while (mapOverlap(getOffsets(greyDirection)));255 directions = ['TL', 'TR', 'BL', 'BR'];256 map.push(new Tile(greyTileClr, greyShadowClr, greyDirection));257 offsets = getOffsets(greyDirection);258 shownTiles.push(offsets);259}260function mapOverlap(curOffsets) {261 let threshold = 8; // or nTiles*2262 for (let i = map.length - 1; i >= Math.max(map.length - threshold, 0); i--) {263 if ((Math.abs(shownTiles[i].x - curOffsets.x) < xDistance / 2) && (Math.abs(shownTiles[i].y - curOffsets.y) < yDistance / 2)) {264 return true;265 }266 }267 return false;268}269// function randomUnrepeatedDirection() {270// let d;271// do {272// if (directions.length === 0) {273// addGreyTile();274// directions = ['TL', 'TR', 'BL', 'BR'];275// }276// shuffle(directions);277// d = directions[0];278// directions.splice(0, 1);279// } // not going reverse: not opposite to either the last tile or the next tile280// while (mapOverlap(getOffsets(d)));281// // (getOppositeDirection(clrDirection) === clrSegments[0][1]) || 282// directions = ['TL', 'TR', 'BL', 'BR'];283// return d;...
MarkerUtils.js
Source: MarkerUtils.js
...70 canvas.height = size[1];71 // const c = document.getElementById("container");72 // c.insertBefore(canvas, c.children[0])73 const ctx = canvas.getContext("2d");74 const offSet = getOffsets(iconColor, iconShape);75 ctx.drawImage(extraMarkers.images[0], 4, 31, 35, 16); // shadowImage76 ctx.drawImage(extraMarkers.images[1], Math.abs(offSet[0]), Math.abs(offSet[1]), size[0], size[1], 0, 0, size[0], size[1]); // iconImage77 // glyph78 ctx.font = "14px FontAwesome";79 ctx.fillStyle = "rgb(255,255,255)";80 ctx.textBaseline = "middle";81 ctx.textAlign = "center";82 ctx.fillText((MarkerUtils.getGlyphs("fontawesome"))[iconGlyph] || '', (size[0] / 2) - 2, (size[1] / 2) - 7);83 const data = canvas.toDataURL("image/png");84 canvas = null;85 return data;86 }87 return null;88 },89 matches: (style, marker) => {90 return style.iconColor === marker.color && style.iconShape === marker.shape;91 },92 getStyle: (marker) => {93 return {94 iconColor: marker.color,95 iconShape: marker.shape96 };97 },98 getGrid: () => {99 return extraMarkers.shapes.map((s) => ({100 name: s,101 markers: extraMarkers.colors.map((m) => ({102 name: m,103 width: extraMarkers.size[0],104 height: extraMarkers.size[1],105 offsets: getOffsets(m, s),106 style: {107 color: m,108 shape: s109 },110 thumbnailStyle: {111 backgroundImage: "url(" + extraMarkers.icons[0] + ")",112 width: extraMarkers.size[0] + "px",113 height: extraMarkers.size[1] + "px",114 backgroundPositionX: getOffsets(m, s)[0],115 backgroundPositionY: getOffsets(m, s)[1],116 cursor: "pointer"117 }118 }))119 }));120 }121 }),122 getGlyphs: (font) => {123 if (!glyphs[font]) {124 glyphs[font] = loadGlyphs(font);125 }126 return glyphs[font];127 }128};129MarkerUtils.markers = {...
FilterQuery.js
Source: FilterQuery.js
...56 var offsets = {}57 var res = {}58 res["values"] = this.fieldLists.reduce((values, list) => {59 var merged = merge(values, list.getValues(offsets))60 for(var fieldName in list.getOffsets()) {61 if(offsets.hasOwnProperty(fieldName)) {62 offsets[fieldName] += list.getOffsets()[fieldName]63 } else {64 offsets[fieldName] = list.getOffsets()[fieldName]65 }66 }67 return merged68 }, {})69 return res70 }71 generateQuery () {72 var offsets = {}73 var queries = this.fieldLists.map(list => {74 var res = list.getQuery(offsets)75 for(var fieldName in list.getOffsets()) {76 if(offsets.hasOwnProperty(fieldName)) {77 offsets[fieldName] += list.getOffsets()[fieldName]78 } else {79 offsets[fieldName] = list.getOffsets()[fieldName]80 }81 }82 return res83 })84 var res = queries.reduce((q, current, i) => {85 var res = q86 if(i > 0) {87 res += this.concatOperations[i - 1] + current88 } else {89 res += current90 }91 return res92 }, "")93 // set parentheses around AND blocks...
ResourcesMarker.js
Source: ResourcesMarker.js
...68 markers: extraMarkers.colors.filter(c => c).map((m) => ({69 name: m,70 width: extraMarkers.size[0],71 height: extraMarkers.size[1],72 offsets: getOffsets(m, s),73 style: {74 color: m,75 shape: s76 },77 thumbnailStyle: {78 backgroundImage: "url(" + extraMarkers.icons[0] + ")",79 width: extraMarkers.size[0] + "px",80 height: extraMarkers.size[1] + "px",81 backgroundPositionX: getOffsets(m, s)[0],82 backgroundPositionY: getOffsets(m, s)[1],83 cursor: "pointer"84 }85 }))86 }));87 }88 }),89 getGlyphs: (font) => {90 if (!glyphs[font]) {91 glyphs[font] = loadGlyphs(font);92 }93 return glyphs[font];94 }95};96MarkerUtils.markers = {...
script.js
Source: script.js
...25 });26 return offsets;27};28var animateByOffsets = function () {29 var actualStep = actualAnchor(getOffsets('section'));30 //Menu activate31 var stepid = 'a[link="#' + parseInt(actualStep) + '"]';32 $("nav .active-state").removeClass('active-state');33 $(stepid).addClass('active-state');34 //Section active35 var prevStep = (actualStep - 1 < 0) ? 0 : actualStep - 1;36 var nextStep = (actualStep + 1 > getOffsets('section').length) ? getOffsets('section').length : actualStep + 1;37 $("#" + actualStep + " > *").addClass("active");38 if (nextStep != actualStep)39 $("#" + nextStep + " > *").removeClass("active");40 if (prevStep != actualStep)41 $("#" + prevStep + " > *").removeClass("active");42 return actualStep;43};44var playAudioForAnchor = function (bool) {45 if (actualAnchor(getOffsets('section')) == 0) {46 ion.sound.play('son-hochelaga-femme');47 return true;48 }49 else if (bool) {50 ion.sound.stop('son-hochelaga-femme');51 return false;52 }53};54$(function () {55 $(document).bind('scroll', function() {56 // "Disable" the horizontal scroll.57 if ($(document).scrollLeft() !== 0) {58 $(document).scrollLeft(0);59 }60 });61 var audioPlay = false;62 ion.sound({63 sounds: [64 {name: "son-hochelaga-femme"}65 ],66 path: "audio/",67 preload: true,68 multiplay: false,69 volume: 1.070 });71 var offsets = getOffsets("section");72 var nbSections = offsets.length;73 var step = animateByOffsets();74 audioPlay = playAudioForAnchor(audioPlay);75 //Event binding76 $(document).scroll(function (event) {77 step = animateByOffsets();78 audioPlay = playAudioForAnchor(audioPlay);79 });80 var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel"81 $(document).bind(mousewheelevt, function (e) {82 var delta = (mousewheelevt == "DOMMouseScroll")? -e.detail : e.originalEvent.wheelDelta;83 var newStep = step;84 if (delta / 120 > 0) {85 newStep--;...
productOptionsOffsets.js
Source: productOptionsOffsets.js
...8 { additional: false, values: [ {}, {}, {} ] },9 // 2, 210 { additional: false, values: [ {}, {} ] }11 ]12 expect(getOffsets(options)).toEqual([0, 1, 2])13 })14 it('with only additional options', () => {15 const options = [16 // 0, 117 { additional: true, values: [ {}, {} ] },18 // 2, 3, 419 { additional: true, values: [ {}, {}, {} ] },20 // 5, 621 { additional: true, values: [ {}, {} ] }22 ]23 expect(getOffsets(options)).toEqual([0, 2, 5])24 })25 it('with additional option after mandatory option', () => {26 const options = [27 // 0, 028 { additional: false, values: [ {}, {}, {} ] },29 // 1, 230 { additional: true, values: [ {}, {} ] },31 // 3, 332 { additional: false, values: [ {}, {} ] }33 ]34 expect(getOffsets(options)).toEqual([0, 1, 3])35 })36 it('with mandatory option after additional option', () => {37 const options = [38 // 0, 139 { additional: true, values: [ {}, {} ] },40 // 2, 241 { additional: false, values: [ {}, {}, {} ] },42 // 3, 343 { additional: false, values: [ {}, {} ] }44 ]45 expect(getOffsets(options)).toEqual([0, 2, 3])46 })...
getOffsets.test.js
Source: getOffsets.test.js
2import { parseValueAndUnit } from 'utils/index';3describe('getOffsets', () => {4 it('returns the offset properties to an element with defaults', () => {5 const props = { y0: 0, y1: 0, x1: 0, x0: 0 };6 expect(getOffsets(props)).toEqual({7 xUnit: '%',8 yUnit: '%',9 y0: parseValueAndUnit(props.y0),10 y1: parseValueAndUnit(props.y1),11 x0: parseValueAndUnit(props.x0),12 x1: parseValueAndUnit(props.x1),13 });14 });15 it('adds the offset properties to an element with various units', () => {16 const props = { y0: '100px', y1: '-50px', x1: '100%', x0: '300%' };17 expect(getOffsets(props)).toEqual({18 xUnit: '%',19 yUnit: 'px',20 y0: parseValueAndUnit(props.y0),21 y1: parseValueAndUnit(props.y1),22 x0: parseValueAndUnit(props.x0),23 x1: parseValueAndUnit(props.x1),24 });25 });26 it("to throw if matching units aren't provided", () => {27 const props = { y0: '100px', y1: '-50%', x1: '100px', x0: '300%' };28 expect(() => getOffsets(props)).toThrow();29 });...
getOffsets.js
Source: getOffsets.js
1let xOffset = require("./xOffset");2let yOffset = require("./yOffset");3function getOffsets() {4 let offsetStrings = []5 offsets.forEach(offset => {6 offsetStrings.push(xOffset.xOffset(offset[0]) + yOffset.yOffset(offset[1]));7 })8 return offsetStrings;9}10exports.getOffsets = getOffsets;11let offsets = [[1, 2], [-1, 2], [1, -2], [-1, -2], [2, 1], [-2, 1], [2, -1], [-2, -1]];12if (require.main === module) {13 console.log(getOffsets(offsets))...
Using AI Code Generation
1const { getOffsets } = require('playwright/lib/client/selectorEngine');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 const offsets = await getOffsets(page, 'text=Get started');8 console.log(offsets);9 await browser.close();10})();11[ { x: 0, y: 0 } ]
Using AI Code Generation
1const { getOffsets } = require('playwright/lib/client/selectorEngine');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 const offsets = await getOffsets(page, 'text="Get started"');8 console.log(offsets);9 await browser.close();10})();
Using AI Code Generation
1const { getOffsets } = require('playwright-core/lib/web/frames');2const { chromium } = require('playwright-core');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 const frame = page.mainFrame();8 const input = await frame.$('input');9 const offsets = await getOffsets(input);10 console.log('offsets', offsets);11 await browser.close();12})();13offsets {14}
Using AI Code Generation
1const { getOffsets } = require('playwright/lib/server/dom.js');2const { chromium } = require('playwright');3const path = require('path');4(async () => {5 const browser = await chromium.launch();6 const context = await browser.newContext();7 const page = await context.newPage();8 const offsets = await page.evaluate(async () => {9 const { getOffsets } = require('playwright/lib/server/dom.js');10 const div = document.querySelector('div');11 return await getOffsets(div);12 });13 console.log(offsets);14 await browser.close();15})();16 <div id="div1" style="position: absolute; left: 10px; top: 10px; width: 100px; height: 100px; background: red;"></div>17 <div id="div2" style="position: absolute; left: 50px; top: 50px; width: 100px; height: 100px; background: blue;"></div>
Using AI Code Generation
1const { getOffsets } = require('playwright/lib/client/selectorEngine');2const { ElementHandle } = require('playwright/lib/client/selectorEngine');3const { Page } = require('playwright/lib/client/selectorEngine');4const page = await browser.newPage();5await page.setContent(`<div id="div1" style="width: 100px; height: 100px; background: red;"></div>`);6const div1 = await page.$('#div1');7const offsets = await getOffsets(page, div1, 'div1');8console.log(offsets);9{ x: 0, y: 0, width: 100, height: 100, scrollX: 0, scrollY: 0 }10const { getBoundingBox } = require('playwright/lib/client/selectorEngine');11const { ElementHandle } = require('playwright/lib/client/selectorEngine');12const { Page } = require('playwright/lib/client/selectorEngine');13const page = await browser.newPage();14await page.setContent(`<div id="div1" style="width: 100px; height: 100px; background: red;"></div>`);15const div1 = await page.$('#div1');16const boundingBox = await getBoundingBox(page, div1, 'div1');17console.log(boundingBox);18{ x: 0, y: 0, width: 100, height: 100 }19const { getBoxModel } = require('playwright/lib/client/selectorEngine');20const { ElementHandle } = require('playwright/lib/client/selectorEngine');21const { Page } = require('playwright/lib/client/selectorEngine');22const page = await browser.newPage();23await page.setContent(`<div id="div1" style="width: 100px; height: 100px; background: red;"></div>`);24const div1 = await page.$('#div1');25const boxModel = await getBoxModel(page, div1, 'div1');26console.log(boxModel);27{
Using AI Code Generation
1const { getOffsets } = require('playwright/lib/client/selectorEngine');2(async () => {3 const offsets = await getOffsets('css=div');4 console.log(offsets);5})();6const { getOffsets } = require('playwright/lib/client/selectorEngine');7(async () => {8 const offsets = await getOffsets('css=div');9 console.log(offsets);10 const div = await page.$(nth(offsets[0]));11 await div.click();12})();13✓ clicked div:nth-child(1)14const { getOffsets } = require('playwright/lib/client/selectorEngine');15(async () => {16 const div = await page.$('div:nth-child(1)');17 await div.click();18})();19const { getOffsets } = require('playwright/lib/client/selectorEngine');20(async () => {21 const offsets = await getOffsets('css=div');22 console.log(offsets);23 const div = await page.$(nth(offsets[1]));24 await div.click();25})();26✓ clicked div:nth-child(2)
Using AI Code Generation
1const { getOffsets } = require('playwright/lib/client/selectorEngine');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 const element = await page.$('input');8 const result = await getOffsets(element);9 console.log(result);10 await browser.close();11})();12{ x: 8, y: 8 }
Using AI Code Generation
1const { getOffsets } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2const offsets = getOffsets( page, elementHandle );3console.log(offsets);4const { getOffsets } = require('playwright/lib/server/supplements/recorder/recorderSupplement');5const offsets = getOffsets( page, elementHandle );6console.log(offsets);7const { getOffsets } = require('playwright/lib/server/supplements/recorder/recorderSupplement');8const offsets = getOffsets( page, elementHandle );9console.log(offsets);10const { getOffsets } = require('playwright/lib/server/supplements/recorder/recorderSupplement');11const offsets = getOffsets( page, elementHandle );12console.log(offsets);13const { getOffsets } = require('playwright/lib/server/supplements/recorder/recorderSupplement');14const offsets = getOffsets( page, elementHandle );15console.log(offsets);16const { getOffsets } = require('playwright/lib/server/supplements/recorder/recorderSupplement');17const offsets = getOffsets( page, elementHandle );18console.log(offsets);19const { getOffsets } = require('playwright/lib/server/supplements/recorder/recorderSupplement');20const offsets = getOffsets( page, elementHandle );21console.log(offsets);
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!!